1. Linux内核模块
Linux本身是宏内核(单内核),但是又提供了模块这样一种机制,构成了Linux独有的模块特性。
Linux内核模块的特点:
(1)模块本身不被编译入内核镜像,从而控制了内核的大小。
(2)模块一旦被加载,它就和内核中的其他部分完全一样。
2. Linux内核模块操作指令
(1)lsmod
显示系统中已加载的所有模块,实际就是读取并分析“/proc/modules”文件。
注:内核中已安装的模块的信息也存在于“/sys/module”目录下。
(2)insmod
向系统中加载模块。
(3)rmmod
从系统中卸载模块。
(4)modinfo
获取模块信息,包括模块作者、模块的说明、模块所支持的参数以及vermagic。
注:向系统加载模块时,会比较模块的vermagic和Linux内核的vermagic是否一致。若不一致,不加载。
(5)modprobe
它的功能比insmod强大,向系统中加载模块,并同时加载模块所依赖的其他模块。
3. Linux内核模块结构
(1)模块加载函数
当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
(2)模块卸载函数
当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。
(3)模块许可证声明
许可证(LICENCE)声明描述内核模块的许可权限。如果不声明LICENCE,模块被加载时,会受到内核被污染(Kernel Tainted)的警告。
在Linux内核模块领域有很多许可证声明,在大多数情况下,内核模块应遵循GPL兼容许可权权。Linux内核模块最常见的是以MODULE_LICENCE("GPL v2")语句声明模块采用GPL v2。
(4)模块参数(可选)
模块参数是模块被加载时可以传递给它的值,它本身对应模块内部的全局变量。
(5)模块导出符号(可选)
内核模块可以导出的符号(symbol,对应于函数或变量),若导出,其他模块则可以使用本模块中的变量或函数。
(6)模块作者等信息声明(可选)
4. 模块加载函数
Linux内核模块加载函数一般以__init标识声明,典型的模块加载函数:
static int __init initialization_function(void)
{
/* 初始化代码 */
}
module_init(initialization_function);
模块加载函数通过“module_init(函数名)”的形式指定,若初始化成功,返回0,;若初始化失败返回错误编码。
注:在Linux内核里,错误编码是一个接近于0的负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM之类的符号值。总是返回相应的错误编码是种非常好的习惯,因为只有这样,用户程序才可以利用perror等方法把它们转换成有意义的错误信息字符串。
注:在Linux内核中,可以使用request_module(const char *fmt)函数加载内核模块。
5. 模块卸载函数
Linux内核模块加载函数一般以__exit标识声明,典型的模块卸载函数:
static void __exit cleanup_function(void)
{
/* 释放代码 */
}
module_exit(cleanup_function);
模块卸载函数不返回任何值,且必须以"module_exit(函数名)"指定。
6. 模块参数
使用"module_param(参数名, 参数类型, 参数读/写权限)"定义参数。
在装载内核模块时,可以使用"insmod (或modprobe) 模块名 参数名=参数值"的形式传递参数,如果不传递,参数将使用模块内部的缺省值。
注:若模块被内置,就无法insmod了,但是bootloader可以通过在bootargs里设置"模块名.参数名=值"的形式给该内置的模块传递参数。
注:模块被加载后,在/sys/module目录下将出现以此模块命令的目录。当模块有参数并且“参数读/写权限”不为0时,在此模块的目录下将出现parameter目录,其中包含以参数命名的文件节点。这些文件的权限就是传入module_param()的“参数读写权限”,而文件的内容为参数的值。
7. 导出符号
模块可以使用如下宏导出符号:
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名); -- 只适用于包含GPL许可权的模块
导出的符号可以被其他模块使用,使用前只需声明一下即可。
注:模块导出的符号会放入内核符号列表中。Linux的"/proc/kallsyms"文件对应着内核符号列表,它记录了符号以及符号所在的内存地址。
8. 模块的声明与描述
在Linux内核模块中,使用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分别声明作者、描述、版本、设备表和别名。
9. 模块的使用计数
在Linux2.6以后的内核提供了模块计数管理接口:try_module_get(&module)和module_put(&module)。
注:在这里该部分仅仅提一下,具体的到后面再详细分析。
10. 模块编译
# Ubuntu的Linux内核源码位置:若在Ubuntu中加载,取消下面两行的注释
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build
# 开发板的Linux内核源码位置
KERN_DIR = /root/driver/kernel
# 内核模块
obj-m += hello.o
# 内核模块编译标志
#EXTRA_CFLAGS = -g -O0
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
注:模块编译的工作原理就是使用make -C进入到内核源码目录下,然后在内核源码目录下借用内核源码中定义的模块编译规则去编译这个模块,编译完成后,把生成的文件拷贝到当前目录下,完成编译。
注:若一个模块包含多个.c文件(如file1.c、file2.c),则使用如下方式编写Makefile:
obj-m := modulename.o
modulename-objs := file1.o file2.o