以题目一为例进行内核模块编程:
题目1:
- 设计一个模块,要求列出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID。
- 设计一个带参数的模块,其参数为某个进程的PID号,模块的功能是列出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号、进程状态。具体参见教材P336
环境: ubuntu22
认识内核模块编程的基本框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h>
static int hello_init(void) {
return 0; }
static void hello_exit(void) { }
module_init(hello_init); module_exit(hello_exit);
MODULE_LICENSE("GPL");
|
有下述几个问题需要注意:
- 三个头文件必须引入
- 必须注册初始化和退出函数
- 必须有模块许可申明
此外,需要注意的是在内核中我们不能使用用户态函数,也就是说我们不能适用诸如printf,strlen等函数,需要使用专门的内核函数
下面是Makefile模板,下面是最基本,也是最常用的模板,其中为一要注意的问题是obj-m的名字需要与编写内核代码文件的名字对应,也就是说这里obj-m是module1.o,你的.c文件的名字应该是module1.c
1 2 3 4 5 6 7 8
| obj-m:=module1.o KDIR:= /lib/modules/$(shell uname -r)/build PWD:= $(shell pwd)
default: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean
|
编程打印进程信息
第一小题
代码比较简单,先放在这里
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
| #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/init_task.h>
static int hello_init(void) { pr_alert("list start !\n"); struct task_struct *p; printk(KERN_ALERT"名称\t进程号\t状态\t优先级\t父进程号"); int kernel_thread_num = 0; for_each_process(p) { if(p->mm == NULL){ kernel_thread_num++; pr_info("%s\t%d\t%ld\t%d\n",p->comm,p->pid, p->stats,p->rt_priority,p->parent->pid); } } pr_info("kernel thread num is %d\n",kernel_thread_num); return 0; }
static void hello_exit(void) { }
module_init(hello_init); module_exit(hello_exit);
MODULE_LICENSE("GPL");
|
介绍一下一些函数:
- pr_info,pr_alert…:用于内核打印
- for_each_process:通过遍历init_task(0号进程对应的进程标识符)来遍历所有内核线程
1 2
| #define for_each_process(p) \ for (p = &init_task ; (p = next_task(p)) != &init_task ; )
|
这里最后打印了内核线程的数量,我们可以通过linux命令简单验证,我们通过内核线程的组id为0这一特点计算内核线程数量
1
| $ ps -e -o pgid | awk '$1 == 0' | wc -l
|
易比较得到一致的结果
第二小题
第二小题也不难,主要是对内核提供的一些宏的应用
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 48 49
| #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/init_task.h>
static pid_t pid = 1; module_param(pid,int,0644);
static int hello_init(void) { pr_alert("list start !\n"); struct task_struct *task = pid_task(find_get_pid(pid), PIDTYPE_PID); struct task_struct *parent = task->parent; struct task_struct *sibling; struct list_head *list; pr_info("Root: Name: %s, PID: %d, State: %ld\n",task->comm,task->pid,task->stats);
pr_info("Parent: Name: %s, PID: %d, State: %ld\n", parent->comm, parent->pid, parent->stats);
list_for_each(list, &task->children) { sibling = list_entry(list, struct task_struct, sibling); pr_info("Child: Name: %s, PID: %d, State: %ld\n", sibling->comm, sibling->pid, sibling->stats); }
list_for_each(list, &parent->children) { sibling = list_entry(list, struct task_struct, sibling); if (sibling != task) { pr_info("Sibling: Name: %s, PID: %d, State: %ld\n", sibling->comm, sibling->pid, sibling->stats); } } return 0; }
static void hello_exit(void) { }
module_init(hello_init); module_exit(hello_exit);
MODULE_LICENSE("GPL");
|
还是简单介绍一下:
- module_param(name,type,perm):声明模块的名字,分别为变量名,变量类型和权限
- list_for_each:一个用于帮助遍历的宏
- list_entry:用于帮助类型转换