技术交流

好好学习,天天向上。

0%

第二章——构造和运行模块

注意事项

内核代码不能假定自己是单线程执行的,多核心系统中,内核同一处代码可能同时在多个核心上执行。哪怕系统只有一个核心,也是无法保证代码是单线程执行执行的。内核中往往有中断和抢占导致同一处内核代码被重复调用。所以内核代码应当考虑可重入、资源争抢等因素。如果内核代码以模块形式进入内核,那么在模块加载到内核之后,内核其他部分就可以看到该模块的所有导出内容了。如果模块加载过程发现异常,还要考虑怎么妥善的处理错误,尤其有可能其他内核代码在本模块尚未初始化完全时就已经在使用本模块的代码了、

内核编译

在内核编译之前,必须安装配套的构建软件,其中包括目标内核版本的头文件。 在CentOS下可以通过groupinstall一步到位安装。内核代码的各个模块是用make实现构建的,所以其基本构建配置文件是Makefile。内核自己实现了一套构建系统——KBuild,这套系统不仅可以简化内核的编译前配置过程,还可以简化内核模块的Makefile的编写方法。一个常用的Makefile模板如下所示

1
2
3
4
5
6
7
8
9
10
11
# 如果已经读取内核构建树,则KERNELRELEASE变量已定义,再次读取Makefile的时候,将走第一个分支
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
# 若则KERNELRELEASE变量未定义,则让Make走下面这个分支
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
# default是Make中的默认构建目标,不是和if else同类型的控制语句
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

这个Makefile被make读取两次,第一次引导Make去加载内核配置,第二次完成本模块构建。Make的-C参数指的是在读取Makefile或者执行其他动作前修改到该目录。M参数则是被内核相关的脚本读取,指定要编译的模块源码所在路径。更多关于KBuild的知识点可以看这里

加卸载

自己编译模块insmod加载,内核自带的模块可以用modprobe加载,modprobe会自己处理模块间的依赖关系。卸载模块用rmmod,处于使用中的模块和不可卸载的模块是无法卸载的。加卸载模块是调用系统调用实现的,在调试内核模块的时候可以给系统调用加断点,实现模块初始化和卸载流程的调试。

初始化函数和清除函数

模块初始化和清除函数的常用写法如下所示。初始化和清除函数都不接受参数,也没有返回值。带下划线的修饰符是可选的,这主要是方便内核优化内核空间大小,在必要时,内核可以知道代码段的哪些内容可以丢弃。

1
2
3
4
5
6
7
8
9
10
11

static int __init initialization_function(void)
{
/* Initialization code here */
}
static void __exit cleanup_function(void)
{
/* Cleanup code here */
}
module_init(initialization_function);
module_exit(cleanup_function);

模块可以接受参数,但是不是从初始化函数指定的。*_init修饰符宏将把initialization_function编译到一个特殊的代码段,然后在模块加载的时候module_init将触发initialization_function*调用。