一、具体任务

设计一个带参数的模块,其参数为某个进程的 PID 号。模块的功能是列出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID 号、进程状态。

二、模块构成

一个模块主要由2部分构成,即 .c文件 以及 Makefile 文件。
而在.c文件的构成中又包括文件头(包含的库函数),初始化函数(init),清除函数(exit),module_init module_exit函数和最后的模块许可声明。
在Makefile文件中包括一个指定生成.ko文件的语句,指向内核以及指向模块源目录的语句,default和clean语句。

三、代码解析

①创建一个module2.c文件(vim module2.c)

在这里插入图片描述
#include <linux/init.h>、#include <linux/module.h>是模块编程必需头文件;
<kernel.h>中包含了内核打印函数 printk函数等;
<moduleparam.h>允许用户传递参数;
<sched.h>包含了定义了进程运行可能处于的状态,提供task_struct
在这里插入图片描述
函数原型 module_param(name, type, perm);
参数name,type是自己定义的变量的类型,perm是权限

perm可取值:
#define S_IRUSR 00400 文件所有者可读
#define S_IWUSR 00200 文件所有者可写
#define S_IXUSR 00100 文件所有者可执行
#define S_IRGRP 00040 与文件所有者同组的用户可读
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IROTH 00004 与文件所有者不同组的用户可读
#define S_IWOTH 00002
#define S_IXOTH 00001

而针对网络上清一色的’0644’,解释是这样的:
0755->用户具有读/写/执行权限,组用户和其它用户具有读/写权限;
0644->用户具有读/写权限,组用户和其它用户具有只读权限;
一般赋予目录0755权限,而文件赋予0644权限。
在这里插入图片描述
task_strut 结构包含的是进程描述符内容(例如本题需要的:comm:进程名字、state:进程状态以及pid);
list_head 结构是创建双向循环链表的结构,包含指针next和prev,一般被嵌入到别的结构里面使用。
在这里插入图片描述
函数原型pid_task(find_vpid(pid), PIDTYPE_PID) 结构体类型,用于根据pid和其类型找到对应的task_struct,返回值为pid所对应的结构体类型,定义式是struct task_struct *pid_task(struct pid *pid, enum pid_type type);
printk printk是内核输出,在终端是看不见的。它实际上是为内核提供日志功能, 记录内核信息或用来给出警告。每个printk() 声明都会带一个优先级(即:KERN_ALERT等)使用像 KERN_ALERT这样的高优先级,来确保printk()将信息输出到控制台而不是只是添加到日志文件中。 后面语句中的KERN_ALERT本题可不加。
在这里插入图片描述
list_for_each //双向链表的遍历
原型:list_for_each(pos, head){
for (pos = (head)->next; pos != (head);pos = pos->next)
}
//遍历完以后最终回到head并返回head的地址
list_entry
原型:list_entry(ptr, type, member)
表示在找出ptr指向的链表节点所在的type类型的结构体首地址,member是type类型结构体成员,和ptr类型相同。返回值是ptr指向的节点的首地址,该节点是type类型。作用是用当前节点地址ptr值减去member离type结构体首地址的距离(以0作为首地址)

即:返回值=ptr-(member-type);

最后就得到了ptr节点指向的节点的type类型结构体的首地址。
在这里插入图片描述
从上至下依次是:
清除函数(module2_exit)(自定义,可不写printk等具体代码内容)
module_init(); //执行初始化函数注册
module_exit();//执行清除函数注册
模块许可声明–>(MODULE_LICENSE(“GPL”))
注意 这些都是模块必需,且模块必须通过MODULE_LICENSE 宏声明此模块的许可证,否则在加载此模块时,会收到内核被污染 “kernel tainted” 的警告,有意义的许可证除了 “GPL”,还有“GPL v2”,“GPL and additional rights”,“Dual BSD/GPL”,“Dual MPL/GPL”,“Proprietary”。

②Makefile文件(vim Makefile)

在这里插入图片描述
obj-m:= 后添加的是要生成的模块名称(如这里是module2.o,则生成的模块名称为module2.ko)。一般与.c文件名相同(为了好找)
KDIR:= 后面要指向的是自己已编译过的内核源码目录
PWD:= PWD 是当前目录,就是指向的是shell窗口当前所处位置,一般后面都是加“$(shell pwd)”不变
注意 default:和clean:之后回车不是8个空格,而是一个Tab键,虽然最后效果相同,但是运行是会报错的(一般":"完直接enter是会自动缩进的)
语句内 -C 指定内核源码目录, M 指定模块源码所在目录

四、完整代码

module2.c文件

#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/moduleparam.h>
#include<linux/sched.h>

static pid_t pid=1;
module_param(pid,int,0644);

static int module2_init(void){
	struct task_struct *p;
	struct task_struct *psibling;
	struct task_struct *pchildren;
	struct list_head *L;
	
	//输出当前进程信息
	p=pid_task(find_vpid(pid),PIDTYPE_PID);
	printk(KERN_ALERT"归属\t程序名\t\tPID号\t进程状态\n");
	printk(KERN_ALERT"本身:    %8s%8d%8ld\n",p->comm,p->pid,p->state);
	//输出父进程信息
	if(p->parent==NULL)
		printk(KERN_ALERT"无父进程\n");
	else
                printk(KERN_ALERT"父进程:  %8s%8d%8ld\n",
                p->parent>comm,p->parent->pid,p->parent->state);
	//输出兄弟进程信息
	list_for_each(L,&p->parent->children){
                psibling=list_entry(L,struct task_struct,sibling);
                printk(KERN_ALERT"兄弟进程:%8s\t%d\t%8ld\n",
                psibling->comm,psibling->pid,psibling->state);
        }
	//输出子进程信息
        list_for_each(L,&p->children){
                pchildren=list_entry(L,struct task_struct,sibling);
                printk(KERN_ALERT"子进程:  %8s\t%8d\t%ld\n",
                pchildren->comm,pchildren->pid,pchildren->state);
        }

        return 0;
}


static void module2_exit(void){
        printk(KERN_ALERT"good bye\n");
}


module_init(module2_init);
module_exit(module2_exit);

Makefile文件

obj-m:=module2.o
KDIR:=/usr/src/linux-5.5.6
PWD:=$(shell pwd)

default:
        make -C $(KDIR) M=$(PWD) modules
clean:
        make -C $(KDIR) M=$(PWD) clean                                      

五、运行

注意: 要确保.c文件和Makefile文件在同一目录下
在这里插入图片描述
1、make
在这里插入图片描述
之后文件夹中会多出一堆乱七八糟的东西
在这里插入图片描述
2、(包括此处,之后的步骤需要获取权限“sudo su”)
insmod (.ko文件名)(参数名)=(要输入的参数)
在这里插入图片描述
注意 此处赋予pid的值需要有这个值对应的进程,否则会报错

3、lsmod查看是否安装成功
在这里插入图片描述
4、dmesg查看,成功
在这里插入图片描述

六、模块卸载以及可能报错

rmmod+.ko文件名
在这里插入图片描述
再次查看,卸载成功
在这里插入图片描述
在卸载模块时候可能遇到无法卸载,模块被莫名占用的情况,这是编写内容出错可能会报错(即:在insmod后无任何反应)
此时删除make产生的文件,在重新启动可以消除占用,而且既然会在这里卡着也就不是系统的问题是代码问题导致无端占用,就要重新检查再进行make安装。

其它情况暂时还没遇到。

Logo

更多推荐