分类: LINUX
2012-05-15 21:24:33
遍历进程的链表,打印PCB的相关信息
知识点:
<1>进程控制块(PCB):操作系统为了对进程进程管理,就必须对每个进程在其生命周期内涉及的所有事物进行全面的描述,该结构体就是进程控制块。在linux源码中的对应的结构体为:/**/
struct task_struct {
volatile long state; /*说明了该进程状态*/
long flags;
int sigpending; /*进程上是否有待处理的信号*/
mm_segment_t addr_limit; /*进程地址空间,区分内核进程与普通进程在内存存放的位置不同0-0xBFFFFFFF for user-thead、0-0xFFFFFFFF for kernel-thread*/
volatile long need_resched; /*调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度*/
int lock_depth;
long nice; /*进程的静态优先级*/
unsigned long policy; /*进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER*/
struct mm_struct *mm;/*进程所属于用户空间*/
int processor;
unsigned long cpus_runnable, cpus_allowed;
struct list_head run_list; /*指向运行队列的指针*/
unsigned long sleep_time; /*进程的睡眠时间*/
struct task_struct *next_task, *prev_task;/*将进程的PCB进行连接,init_task是头,通过宏 for_each_process() 进行遍历*/
struct mm_struct * active_mm;/*当进程运行时该值不能为空*/
struct list_head local_page;
unsigned int allocation_order, nr_local_pages;
struct linux_binfmt *binfmt; /*进程所运行的可执行文件的格式*/
int exit_code, exit_signal; /*进程的退出码和退出信号*/
int pdeath_signal; /*父进程终止是向子进程发送的信号*/
unsigned long personality; /*Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序*/
int did_exec;
pid_t pid; /*进程标识符,用来代表一个进程*/
pid_t pgrp; /*进程组标识,表示进程所属的进程组*/
pid_t tty_old_pgrp; /*进程控制终端所在的组标识*/
pid_t session; /*进程的会话标识*/
pid_t tgid; /*进程所在线程组的标识*/
int leader; /*表示进程是否为会话主管*/
struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
/*以上结点用于构建进程树*/
struct list_head thread_group; /*线程组链表*/
struct task_struct *pidhash_next; /*用于将进程链入HASH表*/
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit; /*供wait4()使用*/
struct completion *vfork_done; /*供vfork() 使用*/
unsigned long rt_priority;/*实时优先级,用它计算实时进程调度时的weight值*/
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_value;
struct timer_list real_timer; /*指向实时定时器的指针*/
struct tms times; /*记录进程消耗的时间*/
unsigned long start_time; /*进程创建的时间*/
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; /*记录进程在每个CPU上所消耗的用户态时间和核心态时间*/
/*内存缺页和交换信息;
min_flt, maj_flt累计进程的次缺页数(Copy on Write页和匿名页)和主缺页数(从映射文件或交换设备读入的页面数); nswap记录进程累计换出的页面数,即写到交换设备上的页面数。cmin_flt, cmaj_flt, cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中*/
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable; /*表示进程的虚拟地址空间是否允许换出
/*uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid
euid,egid为有效uid,gid
fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件系统的访问权限时使用他们。
suid,sgid为备份uid,gid*/
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups; /*记录进程在多少个用户组中*/
gid_t groups[NGROUPS]; /*记录进程所在的组*/
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
int keep_capabilities;
struct user_struct *user;
struct rlimit rlim[RLIM_NLIMITS]; /*与进程相关的资源限制信息*/
unsigned short used_math;
char comm[16]; //进程正在运行的可执行文件名
int link_count, total_link_count;
struct tty_struct *tty;/* 进程所在的控制终端,如果不需要控制终端,则该指针为空*/
unsigned int locks;
struct sem_undo *semundo; //进程在信号灯上的所有undo操作
struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
struct thread_struct thread;
struct fs_struct *fs;/* 文件系统信息*/
struct files_struct *files;/* 打开文件信息*/
spinlock_t sigmask_lock;
struct signal_struct *sig; /*信号处理函数*/
sigset_t blocked; /*进程当前要阻塞的信号,每个信号对应一位*/
struct sigpending pending; /*进程上是否有待处理的信号*/
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
u32 parent_exec_id;
u32 self_exec_id;
spinlock_t alloc_lock;
void *journal_info;
};
<2>在内核中有一个全局变量:current(一个指向当前正在运行的结构体指针)的实现原理linux 2.4 #define current get_current() static inline struct task_struct *get_current(void){
struct task_struct *current ;
__asm__("andl %%esp,%0; ":"=r" (current ;) : "0" (~8191UL));
return current;
}
对于linux 2.6之前的内核采用的模式是:进程的PCB和进程的系统空间一块分配。对于linux2.4 来讲给进程分配系统空间和PCB用到的结构体是union task_union
union task_union {
struct task_struct task;
unsigned long stack[INIT_TASK_SIZE/sizeof(long)];
}
从linux 2.6 开始之后内核代码有了很大的改版,因为进程的PCB中要存储的东西又增加了很多,由于进程的系统空加不能动态变化,因此是的进程的系统空间足够使用,将PCB中的一部分信息进行提取(thread_info)取代2.4内核的PCB(下图),使得系统空间和thread_info 一块分配。
<3>双向循环链表:struct list_head{
struct list_head *prev, *next;
}
利用该双向链表将数据域和指针域分开,使得链式结构的适用性变的更强,因此实现了list.h中对于双向链表的一系列操作:
Ø #define llst_for_each(pos,head) \
for(pos=head->next;pos!=(head) ;pos = pos->next)
标注:pos和head都是stuct list_head * 类型,由于构造的双向循环链表,因此判断结束的条件是pos!=(head),此外还需要注意,对于list_for_each()的使用,第一个节点是不会被遍历的,因此一般遍历的链表可能是带头节点,否则需要对第一个节点进行特殊处理。
Ø #define list_entry(ptr,tyoe,mem) \
((type*)((char*)(ptr)-(unsigned long)(&((type*)0)->mem)))
标注:该宏的主要作用是根据结构体中的某个成员地址值获取它所在结构体的变量的地址。主要的思路是根据当前变量的地址 – 该变量在结构体中的偏移量;
偏移量:(unsigned long)(&((type*)0)->mem),其中将0强制转换成为type*类型,然后获取结构体中mem的地址,由于基址是0所以mem的地址就是它的偏移量。那么在用已知的变量地址减去就获得了所在结构体的地址值,并且将他进行强制类型转换
三、程序的分析:该题目是遍历操作系统中所有的进程,并且打印进程的所有信息。由于进程的组织方式有很多种:
Ø 利用struct list_head 组织成为双向链表,因此可以通过list.h中的宏:list_for_each 和 list_entry 配合使用,对进程链表进程遍历和获取进程的相关信息。
Ø 利用 PCB中数据成员:struct task_struct *next_task, *prev_task 和宏for_each_process()进行遍历。
for_each_process(task)\
for( pos =&init_task ;(task=task->next)!=&init_task;)
Ø 利用哈希散列表进行存储。
四:代码:
process_mess1.c
Makefile 文件
---------------------------------------------------------------------------
运行命令
命令:
make
sudo insmod process_mess1.ko
dmesg
sudo rmmod process_mess1