一、背景
linux内核框架庞大,如果把所有需要的功能都编译到linux内核中,会导致两个问题:
1、生成的内核非常大。
2、新增或删除功能需要重新编译内核。
linux为了解决上述问题,引入了模块机制,使得编译出的内核并不需要包含所有功能,而在这些功能需要被使用的时候,动态加载加载相应代码到内核中。
模块具有这样的特点:
1、模块本身不被编译入内核映像,从而控制住了内核大小。
2、模块一旦被加载,它就和内核中其它部分完全一样。
二、编译方法
object-y:编译进内核
object-m:编译成模块(xxx.ko)
三、模块加载方法
1、insmod moduleName.ko
使用rmmod moduleName 进行卸载
2、modprobe
modprobe -r 进行卸载
modprobe比insmod 强大,它在加载模块时,会加载模块所依赖的其它模块;卸载时会卸载依赖的模块。
四、模块的构成
1、模块加载函数
(1)在模块加载时,模块的加载函数会被内核执行,完成本模块的相关的初始化工作,使用 module_init(funcName);进行声明。
(2)在内核代码中可以使用request_module()函数动态加载模块。
(3)内核加载函数一般以_ _init 标识。
在linux中,所有标识为_ _init 的函数如果直接编译进内核,在链接的时候都会放在.init.text区段。
#define _ _init _ _attribute_ _ ((_ _section_ _(".init.text")))
所有的_ _init函数在区段.initcall.init.中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些_ _init函数,并在初始化完成后释放init区段(包括.init.text、.initcall.init等)的内存。
另外,还可以定义初始化阶段需要的数据,使用_ _initdata进行标识,内核在初始化完成后,也可以释放他们占用的内存。
2、模块的下载函数
(1)在模块卸载时,模块的卸载函数会被内核执行,完成与模块加载相反的工作,使用 module_exit(funcName);进行声明。
(2)模块的下载函数一般以_ _exit进行标识。如果模块被直接编译进内核,卸载函数会被忽略,不被编入内核。这是因为模块被编入内核了,就不可能卸载了,所以卸载函数就没有必要存在了
3、模块许可证声明
许可证声明描述内核模块的许可权限,如果不声明权限,模块加载时将收到内核被污染(Kernel Tainted)的警告。常用的LICENSSE有GPL、GPL v2、GPL and additional rights、Dual BSD/GPL、Dual MPL/GPL等,使用MODULE_LICENSE(LICENSE)进行声明
4、其它可选函数
MODULE_AUTHOR("XXX");-----------模块作者
MODULE_DESCRIPTION("XXXXXXX");----------模块描述