打印进程树

 

1

  打印进程树首先要对linux进程结构有具体的了解,其次要寻找相应算法完成对所有进程的遍历.我正是基于此需要展开了试验.

进程

  进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

它是操作系统结构的基础;是一个正在执行的程序;计算机中正在运行的程序实例;可以分配给处理器并由处理器执行的一个实体;由单一顺序的执行显示,一个当前状态和一组相关的系统资源所描述的活动单元。

  进程为应用程序的运行实例,是应用程序的一次动态执行。

  进程的特征:

  动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。

  并发性:任何进程都可以同其他进程一起并发执行

  独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;

  异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进

  结构特征:进程由程序、数据和进程控制块三部分组成。

1.2 task_struct

  task_structlinux进程描述符的数据结构,其定义位置在include/linux/sched.h,其信息组成包括:

进程状态信息(state, flags,ptrace)

调度信息(static_prio,normal_proi, run_list, array, policy)

内存管理(mm, active_mm)

进程状态位信息(binfmt,exit_state, exit_code, exit_signal)

身份信息(pid, tgid, uid,suid, fsuid, gid, egid, sgid, fsgid)

家族信息(real_parent, parent,children, sibling)

进程耗间信息(realtime, utime,stime, starttime)

时钟信息(it_prof_expires,it_virt_expires, it_sched_expires)

文件系统信息(link_count, fs,files)

IPC信息(sysvsem, signal, sighand, blocked, sigmask, pending)

  本次实验所要用到的是其家族信息parentchildrensibling

1.3 list_head

   childrensibling都是list_head结构的变量,list_head其实是一个简单的双向循环链表结构,其结构定义为:

   struct list_head {

         struct list_head *next,*prev;

   };

 list_head结构用于构造双向环形链表的内置函数:

   LIST_HEAD(head) : 定义一个空表头

                    struct list_head head = {&head,&head};



   INIT_LIST_HEAD(head) : 初始化一个已定义的表头;

                    head->next = head;

                    head->prev = head;



   list_add(entry,head);   entry添加到head之后,用于构造堆栈

                    head->next->prev = entry; 

                    entry->next = head->next; 

                    entry->prev = head;               

                    head->next = entry;



   list_add_tail(entry,head) : entry添加到head之前,用于构造队列

                    head->prev = entry;

                    entry->next = head;                             

                    entry->prev = head->prev;

                    head->prev->next = entry;



   list_del(entry) : 删除entry

                    entry->next->prev = entry->prev;

                    entry->prev->next = entry->next;       



   list_del_init(entry) : 删除并复位entry

                    entry->next->prev = entry->prev;

                    entry->prev->next = entry->next;       

                    entry->next = entry;

                    entry->prev = entry;

                    

   list_empty(head) : 测试环形链表是否为空

                    (head->next == head)



   list_splice(list,head) : 将两个环形链表合成一个大表

                    list->prev->next = head->next;

                    list->next->prev = head;

                    head->next->prev = list->prev;

                    head->next = list->next;



   list_entry(ptr,type,member) : 如果type结构中member的地址是ptr,则返回type结构的地址

                    ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))



   list_for_each(entry,head) : 遍历链表

                    for (entry = (head)->next; entry != (head); entry = entry->next)

 

 

 

 

2 代码解析

#ifndef __KERNEL__

#define __KERNEL__

#endif

#ifndef MODULE

#define MODULE

#endif //首先明确这是一个模块

#include <linux/sched.h>

#include <linux/unistd.h>

#include <linux/list.h>

#include <linux/init.h>

#include <linux/module.h>//任何模块程序的编写都需要包含linux/module.h这个头文件

#include <linux/kernel.h>

MODULE_LICENSE("Dual BSD/GPL");//

void pstreea(struct task_struct* p,int b)

{int i;

for(i=1;i<=b;i++)

printk("   ");

printk("|---%s/n",p->comm);

struct list_head* l;

for (l = p->children.next; l!= &(p->children);l = l->next)//作用同list_for_each()

{struct task_struct*t=list_entry(l,struct task_struct,sibling);//children链上的某一节点作为sibling赋给task_struct

pstreea(t,b+1);                                         //实现了向children节点的移动

}

}

static int pstree_init(void)

{

struct task_struct* p;

int b=0;

for ( p = current; p != &init_task; p = p->parent ) ;//回溯到初始父进程

pstreea(p,b);

return 0;

}

static void pstree_exit(void)

{ 

printk("Hello, kernel!/n");  //注意在这儿使用的是printk()函数(不要习惯性地写成printf)printk()函数是由Linux内核定义的,功能与printf相似。字符串<1>表示消息的优先级,printk()的一个特点就是它对于不同优先级的消息进行不同的处理,之所以要在这儿使用高优先级,是因为默认优先级的消息可能不能显示在控制台上。这个问题就不详细讲了,可以用man命令寻求帮助。

}

module_init(pstree_init);

module_exit(pstree_exit);//函数init ()和函数exit ( )是模块编程中最基本的也是必须的两个函数。init ()                          向内核注册模块所提供的新功能;exit ()负责注销所有由模块注册的功能。

3 结束语

这次试验一波三折,刚开始由于没有发现助教给的blog链接,结果导致决定开始做得第一个晚上完全陷在如何合法调用内核头文件的泥潭里,查找了好多资料发现可能需要编写一个叫做内核的东西,但东拼西凑的方法并没让我编译一个成功的.ko出来。可当第二天发现助教的blog我就有一种要吐血的感觉……我之前整整一晚的尝试摸索还比不住按照助教给的方法跑一遍!哎一晚上的时间就这样完全做了无用功 无语……解决了调用问题,就要去思考具体的代码实现。于是经过又一个晚上的奋战,我知道了该用深度优先的遍历来打印进程树,还知道了在很恶心的list结构里我根本没办法向子进程方向移动!*_0于是在第二天课间问了叶老师,老师讲了很多,但我只记住了一句话“其实是有办法的,去年有人用十几行代码就做出来了”。于是又有了一通宵的思考尝试,我觉得我做出了一个不错的算法,正确性也反复推敲演绎过,很自信的认为可以输出完美的进程树了。但事与愿违,在最后跳出了段错误(segmentation fault)。于是我想了各种办法寻找原因,最后得出的结论是可能进程数较多递归深度过大造成的栈溢出问题。并很自信的把代码发给了叶老师。 在看了ppt上去年做好的pstree例子,发现里面正好有和我思路相同的,收获很多.因为对递归理解不深,别人十几行的代码我却用了上百行的代码,无语啊!最后对代码进行了大面积的修改终于完成了,但觉得很不爽,感觉遭遇这么多悲剧最后还是在学习别人的基础上完成的,很受打击。

参考资料:

1.南京大学叶保留老师linux系统课程助教提供的讲稿与课内资料(2009 9月)

2.数据结构(用面向对象方法与C++语言描述)(第2版),殷人昆 ,清华大学出版社

3.Linux Kernel Development ,Second Edition,Robert Love,China Machine Press

 


 

Logo

更多推荐