遍历进程链表

我们知道,一个进程是由进程控制块(PCB),代码段和数据段组成的;并且,OS通常是通过PCB来感知一个进程的存在。其实PCB就是操作系统对每个进程的代码描述。linux内核中使用task_struct结构来描述一个PCB(具体可以在linux/kernel/sched.c查看源码);多个进程则常常使用双链表等来进行组织。比如可运行状态的进程组成可运行队列,等待状态的进程组成等待队列等。

本文将使用前文中所分析的list_head结构来遍历内核中的进程链表。task_struct结构中使用多个字段来详细的描述一个进程。本文中所属的遍历函数只打印一些常用的信息,即进程名称,进程的pid。

我们利用list.h中的下述遍历宏对整个进程链表进行遍历:

1 420#define list_for_each_entry(pos, head, member)                          \
2 421        for (pos = list_entry((head)->next, typeof(*pos), member);      \
3 422             prefetch(pos->member.next), &pos->member != (head);        \
4 423             pos = list_entry(pos->member.next, typeof(*pos), member))

先简单了解一下这个宏的使用方法。由于list_head结构中没有数据字段,所以它经常被嵌套在其他的结构体当中。pos指向包含list_struct结构的结构体;head为list_head类型的指针,我们可以使用链表中任意一个结点的指针;member即为list_head类型的变量名。

对应到我们下面的程序,pos为指向task_struct类型的指针,我们通过遍历宏即得到每个进程对应的task_struct类型的指针;我们将current_head赋予遍历宏中的第二个参数,current是一个宏,即为系统内正在运行的进程;由于list_struct结构在task_struct结构中的变量明为tasks,因此我们将tasks传递给便利宏的第三个参数。

01 #include< linux/init.h >
02 #include< linux/module.h >
03 #include< linux/sched.h >
04 #include< linux/sem.h >
05 #include< linux/list.h >
06  
07 static int __init  traverse_init(void)
08 {
09       struct task_struct *pos;
10       struct list_head *current_head;
11       int count=0;
12  
13       printk("Traversal module is working..\n");
14       current_head=&(current->tasks);
15       list_for_each_entry(pos,current_head,tasks)
16       {
17              count++;
18              printk("[process %d]: %s\'s pid is %d\n",count,pos->comm,pos->pid);
19       }
20       printk(KERN_ALERT"The number of process is:%d\n",count);
21       return 0;
22 }

了解了便利宏的使用方法,那么理解上述代码就简单的多了。关于便利宏的代码详解可参考这里的文章。

加载函数要做的工作就是这些,那么卸载函数的功能就相对于简单多了。

1 static void __exit traverse_exit(void)
2 {
3   printk("hello world exit\n");
4 }

OK,编译,插入内核,再查看内核日志文件吧!

实验题目如下:1. 实验题目 1.1 实验目的 通过本实验的学习,掌握信创操作系统内核定制中所常用的内核数据结构和函数,具体包 括:i.内核;ii. 内核内存的分配释放; iii.内核线程;iv.内核锁; 1.2 实验内容 设计一个内核模块,并在此内核模块中创建一个内核以及两个内核线程。 • 线程1需要遍历进程链并将各个进程的 pid、进程名加入到内核中。 • 线程2中需不断从内核中取出节点并打印该节点的元素。 在卸载模块时停止内核线程并释放资源。 2. 实验结果 使用 dmesg 命令可以观察到当前系统中运行的所有进程的 pid 与进程名。请按照如下方式一步一步教我如何实现,注意一定要一步一步来:初始化: 创建一个链用于保存进程的 PID 和进程名。 初始化自旋锁,以保证多线程环境下链操作的安全性。 创建两个内核线程: 线程1:遍历进程链,将每个进程的 PID 和进程名保存到内核。 线程2:从链中取出元素并打印进程信息。 线程1: 使用 for_each_process 宏遍历系统中的所有进程。 为每个进程创建一个链节点,保存其 PID 和进程名,并将其加入链。 线程2: 不断从链中取出节点,打印进程的 PID 和进程名,并释放内存。 需要对链的访问加锁,以避免与线程1的冲突。 模块卸载: 停止线程。 清空链,释放所有节点的内存。 函数:kernel_module_init() 初始化链 my_list 初始化自旋锁 lock 创建线程1,执行 thread1_func() 如果线程1创建失败: 打印错误信息并返回错误代码 创建线程2,执行 thread2_func() 如果线程2创建失败: 停止线程1,打印错误信息并返回错误代码 返回 0 函数:thread1_func() while (线程未停止): 进入读保护 (rcu_read_lock) 遍历系统中所有进程 (for_each_process): 为每个进程分配节点内存 将进程 PID 和 进程名 保存到节点 加锁 将节点插入链尾部 解锁 退出读保护 (rcu_read_unlock) 休眠 5 秒 函数:thread2_func() while (线程未停止): 加锁 遍历中的每个节点: 打印 PID 和进程名 从链中删除节点 释放节点内存 解锁 休眠 5 秒 函数:kernel_module_exit() 停止线程1 停止线程2 加锁 遍历并清空链: 释放每个节点的内存 解锁 打印 "模块卸载成功" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 关键步骤详解 线程1 使用 for_each_process 宏遍历系统中的所有进程,使用 kmalloc 动态分配内存来存储每个进程的 PID 和进程名,最后将其加入链。 线程2 定期从链中取出并打印进程信息,并释放节点的内存。 自旋锁 用来保证线程1和线程2对链的访问不会出现竞争条件。 卸载模块 时,停止所有线程并清理链中的节点。 操作步骤 登录虚拟机 本实验使用信创操作系统,登录方法较为特殊。 (1)复制登录命令,登录Pod (2)复制Pod登录密码 (3)复制登录命令,登录虚拟机 (4)复制虚拟机登录密码 至此,你应该已经进入虚拟机内部,输入命令即可操纵机器。 查找资源文件 本次实验文件位于~/lab/1目录下,cd lab/1进入目录 而后将我们已经在本地编辑好的代码复制进去(通过vim),此处需查询vim使用手册。 输入vi 文件名查看文件,按ESC进入编辑模式,而后输入dd删除光标当前行及以下的部分,转到本机编辑好的文件,复制,右击鼠标即可将当前剪贴板里的内容粘贴进去,按ESC,输入:wq保存并退出。 编译模块 输入make,进行编译(应该不会报错,我没有报错,若有错误请自行百度)。
最新发布
04-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值