Linux内核编译(通过内核模块显示进程控制块信息)
实验说明
在内核中,所有进程控制块都被一个双向链表连接起来,该链表中的第一个进程控制块为init_task。编写一个内核模块,模块接收用户传递的一个参数num,num指定要打印的进程控制块的数量﹔若用户不指定num或者num<0,模块则打印所有进程控制块的信息。需要打印的进程控制块信息有:进程PID和进程的可执行文件名。
解决方案
(1)定义模块参数
该模块需要接受用户传递的参数,在使用该参数之前,需要在代码中预先定义好该参数,将该参数的类型设置为整型,并且在sysfs文件系统中的权限是只读的。定义的方法为:
static int num=–1;
module_pararm(num, int, s_IRUGO);
该参数的初始值被设置为-1。-1将作为打印所有进程控制块的标记,默认值为-1,意味着当用户不传入任何参数时,模块将打印所有的进程的信息。
(2)访问进程控制块链表
在内核中,进程控制块被组织成多个双向链表,其中有一个双向链表包含所有的进程控制块,只需要访问该双向链表,就可以访问到所有进程控制块。Linux内核中几乎所有双向链表都采用相同的数据结构来实现,内核中定义list_head通用数据结构,其定义如下:
struct list_head {
struct list head *next,*prev;
};
list_head 中,next指向链表中的下一个list_head 数据结构,prev指向链表中的前一个list_head 数据结构。之所以说该结构是用于实现一个通用的双向链表是因为:如果一个数据结构包含list_head结构,开发者就可以通过内核提供的一组宏创建并操作一个双向链表,而该链表中元素的类型为该数据结构,如图12-1所示。
在进程控制块task_struct中,包含一个名为tasks的成员,该成员的类型为list_head,这意味着进程控制块能够通过该成员将进程控制块串成一个双向链表。Linux 内核通过该成员将所有的进程都放入同一个双向链表,因为 list_head 结构中的 next 和 prev指针并不是指向包含list_head的数据结构,而是指向另一个list_head 数据结构。为了访问包含list_head 的数据结构,内核提供一个宏:
list_entry(ptr,type,member);
在该宏中, ptr是一个指向list_head的指针, type是包含list_head的数据结构类型,而member是list_head在该数据结构中的成员名。例如,若一个进程控制块中的tasks 的地址为p,为了访问该进程控制块,可以采用:
list_entry(p,struct task _struct,tasks);该宏便会返回该进程控制块的地址。
知道如何使用双向链表后,就可以方便地访问内核中所有的进程控制块,因为它通过tasks成员串成一个双向链表,如果得到一个进程控制块的地址p,开发者可以通过:
list_entry(p->tasks.next, struct task _struct, tasks);
访问该双向链表中的下一个进程控制块。在该双向链表中,第一个进程控制块为init_task,如果开发者发现下一个进程控制块为init_task时,说明已经完整地遍历过所有进程控制块。
内核定义宏for_each process 用于遍历所有的进程控制块,开发者通过该宏就能将所有的进程控制块访问一遍,该宏展开的形式为:
for (p = &init_task ; (p = list_entry(§->tasks.next, struct task_struct, tasks) !=&init_task ; )(3)输出进程控制块信息
进程控制块中包含进程大部分信息,根据实验要求,模块需要打印进程的pid和可执行文件名,在进程控制块的数据结构中,成员pid 为进程的PID,而成员comm包含进程的可执行文件名。在内核中,模块可以通过printk()内核函数将这些信息打印到系统日志中。
listprocess.c文件程序框架
实验步骤
对listprocess.c文件进行补充
在模块清理函数中加入:
printk("Good bye.\n");
将listprocess.c文件与makefile文件放在一个文件夹中
使用终端进入文件夹,直接编译
sudo make
报错一:
error: function declaration isn’t a prototype [-Werror=strict-prototypes]
意思是函数参数表没指明,进入listprocess.c文件在exp_init和exp_exit的括号里加上void
报错二:for_each_process报错
原因是for_each_process函数位置不同,在头文件中加入
#include <linux/sched.h>
#include <linux/kernel.h>
报错三:obj-m:=listprocess.o
/bin/sh: 1: obj-m:=listprocess.o: not found
Makefile:6: recipe for target ‘default’ failed
make: *** [default] Error 127
示例代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
static int num = -1;
module_param(num, int, S_IRUGO);
static __init int exp_init(void){
struct task_struct *p = NULL;
for_each_process(p)
{
if (num==0 )
break;
printk("pid=%d,path=%s\n", p->pid, p->comm);
num--;
}
return 0;
}
static __exit void exp_exit(void){
printk("Good bye.\n");
}
module_init(exp_init);
module_exit(exp_exit);
obj-m:=listprocess.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
打开makefile文件删除最后一句obj-m:=listprocess.o即可
接着在编译一次
在终端输入
sudo demsg
查看结果
卸载内核可以使用
sudo insmod listprocess.ko