作者:罗宇哲,中国科学院软件研究所智能软件研究中心
在上一期中,我们介绍了Linux内核编译方法,这一期我们用一个例子来介绍如何向Linux内核中增加一个模块。
一、LKM内核模块
LKM是Loadable Kernel Module的缩写,意思是可加载内核模块。它有点儿像动态链接库,可在运行时加载,成为内核代码和数据的一部分,访问全部内核地址空间,也可运行时动态卸载(需要解决依赖关系,并释放内存空间),所谓的更新就是加载—>卸载—>加载的过程。LKM是大部分设备驱动、文件系统的存在形式。
为了编写内核模块,我们需要准备好编写的环境。首先要指定内核源码或源码的头文件(即本模块是为哪个内核版本编写的),然后可以用VIM等编辑器编写,最后可以用gcc编译。
下图展示了一个简单的内核模块的示例代码:
在示例代码中我们可以看到两个重要的宏:moduleinit和moduleexit。这两个宏包含在init.h头文件中,规定了模块的入口和出口函数。而MODULELICENSE、MODULEAUTHOR和MODULE_DESCRIPTION三个函数则被包含在module.h头文件中[4]。
为了将这个模块的源码编译成可动态加载的内核模块,我们还需要修改Makefile:
注意,运行模块代码有两种方式,第一种方法是静态编译连接进内核,在系统启动过程中进行初始化;第二种方法是编译成可动态加载的 module,通过 insmod 动态加载重定位到内核。这两种方式可以在 Makefile 中通过obj-y
或obj-m
选项进行选择[1]。使用obj–m
选项编译之后会生成 .ko 文件,可通过insmod
将模块动态加载到内核中。
采用动态加载的方式有两个优点,一是可根据系统需要运行动态加载模块,以扩充内核功能,不需要时将其卸载,以释放内存空间;另一个是当需要修改内核功能时,只需编译相应模块,而不必重新编译整个内核[1]。
有五个与动态加载内核模块有关的命令[2]:
1.insmod:向Linux内核中插入一个模块;
2.rmmod:卸载内核中的模块;
3.lsmod:显示内核中的模块 ;
4.modprobe:可载入指定的个别模块,或是载入一组相依的模块。modprobe 会根据 depmod 所产生的相依关系,决定要载入哪些模块。若在载入过程中发生错误,在 modprobe 会卸载整组的模块[3]。
5.modinfo:显示kernel模块的对象文件,以显示该模块的相关信息。
有时侯一个模块可能要调用其他模块中的函数,如果内核模块要引用内核代码中的符号则要通过内核符号表。内核符号表记录了内核中所有的符号(函数、全局变量等)的地址以及名字,在内核代码中通过printk("%pS\n", addr)
可以打印符号名[5]。 使用EXPORTSYMBOL可以将一个函数或全局变量以符号的方式导出给其他模块使用*,*在使用时应先在被调用函数之后用EXPORTSYMBOL(函数名)将函数导出,然后在调用该函数的模块中用extern关键字引用该函数。在加载模块时应注意顺序,首先加载定义该函数的模块,然后加载调用该函数的模块[6]。内核启动后生成/proc/kallsyms
,它包含了内核中的函数符号和全局变量。
二、结语
本期通过一个实例介绍如何编写和编译一个新的内核模块,下一期我们将对Linux各版本进行对比分析。
参考文献
[1]https://blog.csdn.net/yanxuan321/article/details/86606329
[2]https://blog.csdn.net/CPU1994GHz/article/details/79309221?utm_source=blogxgwz3
[3]https://baike.baidu.com/item/modprobe/7939608?fr=aladdin
[4]https://www.xuebuyuan.com/3181553.html
[5]https://www.cnblogs.com/sky-heaven/p/6297679.html
[6]https://www.cnblogs.com/Caden-liu8888/p/7725293.html