linux内核模块
- 一个简单的驱动
模块的使用能使linux内核便于裁剪,根据不同的应用需求得到一个最小的内核,同时调试内核驱动也更为方便,比如如果调试i2c驱动,如果不采用模块的方式,那么每次修改i2c驱动就得编译整个内核,对于编译调试极其耗时,使用模块,一个简单的insmod就将模块加载进了内核,如果觉得不合适,需要调试,只需要rmmod就可以将模块卸载。
一个简单的驱动模块:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 MODULE_LICENSE("Dual BSD/GPL"); 4 5 static int __init test_init(void) 6 { 7 printk(KERN_ALERT"test modules init\n"); 8 return 0; 9 } 10 11 static void __exit test_exit(void) 12 { 13 printk(KERN_ALERT"test modules exit\n"); 14 } 15 16 17 module_init(test_init); 18 module_exit(test_exit);
这个驱动什么都没干,就打印了几句话,但是具备了驱动程序最基本的几个要素:
- 驱动初始化函数 XXX_init(void),使用宏module_init(test_init);告诉内核这是模块的初始化函数入口。
- 驱动退出函数XXX_exot(void),使用宏module_exit(test_exit);告诉内核这是模块的退出函数入口。
- 驱动模块遵循的协议 MODULE_LICENSE("Dual BSD/GPL")
注意到初始化函数和退出函数分别被__init,__exit修限定,其实不用它们来限定2个函数也是可以的,那么用不用这2个东西限定有什么区别呢?首先看它们到底是2个什么东西:
1 #define __init __section(.init.text) __cold notrace 2 #define __exit __section(.exit.text) __exitused __cold notrace
是2个宏,其中__section也是个宏
1 # define __section(S) __attribute__ ((__section__(#S)))
__attribute__是针对gcc的特有关键字,__attribute__ 可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute ),我见的比较多的是在设置数据结构的对齐属性上:
1 struct S { 2 3 short b[3]; 4 5 } __attribute__ ((aligned (8)));
在这里设置为.init.text是指把这个函数放到特殊的.init.text段,看过汇编代码就可能有映象.text段,也就是代码段,函数都是放这个段的。.init.text段的特性是等模块加载完了,这个段将被释放,因为就好比我们写一个单片机程序一样,一般都会有个init函数,它完了就是个死循环,驱动实际在运行过程中XXX_init函数就没有什么作用了,因此就可以把XXX_init函数加载到内存的那段释放掉,腾出空间来给别人用。
__exit就是在卸载模块完成后,将它标识的函数所占用的内存释放。
除了__init,__exit,还有__initdata,__exitdata。作用基本类似,我原本猜想带data的应该是修饰仅在初始化函数和退出函数里面用到的全局变量,比如一个数组之类的东西,但是我试了用__initdata修饰函数好像编译并不报错,暂时不深究。
MODULE_LICENSE("Dual BSD/GPL"),少了它不影响编译,但是加载模块的时候会警告提示这个模块污染了内核:
[22321.826339] test: module license ‘unspecified‘ taints kernel.
[22321.826349] Disabling lock debugging due to kernel taint
[22321.826759] test modules init
- 编译并加载,卸载这个驱动
编译模块的makefile
1 ifneq ($(KERNELRELEASE), ) 2 obj-m := test.o book.o 3 else 4 KERNELDIR ?= /lib/modules/$(shell uname -r)/build 5 PWD := $(shell pwd) 6 defualt: 7 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 8 endif 9 10 clean: 11 rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers 12 13 .PHONY:clean
加载模块:
1 sudo insmod test.ko
为了图方便,使用一个简单的shell脚本监视dmesg的输出,脚本如下:
1 #!/bin/bash 2 3 set -o nounset # Treat unset variables as an error 4 5 while true 6 do 7 dmesg -c 8 sleep 1 9 done
卸载模块:
1 sudo rmmod test
卸载模块不需要后面的.ko。
- 指定和导出模块的参数
参数在驱动里面必须是一个全局变量,一般加static限定。
声明参数,使用宏:
1 module_param(<参数名>, <参数类型>, <参数读写权限>);
参数类型支持:byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool 或 invbool(布尔的反)
例如:
1 static int num = 5; 2 3 module_param(num, int, S_IRUGO); 4 5 static int __init test_init(void) 6 { 7 printk(KERN_ALERT"num:%d\n",num); 8 return 0; 9 }
如果在insmod的时候,指定num的值:
1 insmod num=10
那么输出:num:10
需要注意的是,参数在声明时要给一个默认值,因为如果insmod指定参数值,那么它就使用这个默认值。
一个参数(在驱动里面的一个全局变量)怎么会有读写权限?加载这个模块后,进入/sys/module目录,会看到一个test的目录,这个目录记录了这个test模块的一些信息,与未加模块参数的时候相比,它多出了一个parameters的目录,进如这个目录,发现会有个与模块参数同名的num文件,cat它里面的内容就是它的默认值5,再查看这个num文件的权限:
-r--r--r-- 1 root root 4096 5月 3 21:30 num
和我在代码中指定的一样,原来权限就是指这个文件的权限,参数与之对应的一个文件,也就是说参数的默认值是可以被更改的,只要权限拿到了,而我们使用insmod num=10的方法并不是修改这个默认值(很明显,我指定num为10成功了,但是我并没有对num文件的写权限),只是在加载模块时覆盖变量的值。
使用 cat /proc/kallsyms | grep "\[test\]"查看与test.ko这个模块相关的符号:
1 00000000 t test_exit [test] 2 00000000 r __param_num [test] 3 00000000 r __param_str_num [test] 4 00000000 d num [test] 5 00000000 d __this_module [test] 6 00000000 t cleanup_module [test]
可以看到2个我们预料中的符号,num和test_exit,如果我们不使用__init限定test_init,那么可以看到test_init也在里面,也就证明了__init的作用是在调用初始化函数后释放了与之对应的内存:
1 00000000 t test_init [test] 2 00000000 t test_exit [test] 3 00000000 r __param_num [test] 4 00000000 r __param_str_num [test] 5 00000000 d num [test] 6 00000000 d __this_module [test] 7 00000000 t cleanup_module [test] 8 00000000 t init_module [test]
使用module_param是指外面可以传一个值进来,如果要这个值对外可见,我们明显不能采用去掉static限定的办法,而是使用EXPORT_SYMBOL(num);这个宏是指将模块内的全局变量num对外可见,它不仅仅可以用于变量也可以对函数起作用。
模块A:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 MODULE_LICENSE("Dual BSD/GPL"); 4 5 static int num = 5; 6 module_param(num, int, S_IRUGO); 7 8 static int __init test_init(void) 9 { 10 printk(KERN_ALERT"test modules init\n"); 11 return 0; 12 } 13 14 static void test_saymyname(void) 15 { 16 printk(KERN_ALERT"you call me\n"); 17 } 18 static void __exit test_exit(void) 19 { 20 printk(KERN_ALERT"test modules exit\n"); 21 } 22 23 24 module_init(test_init); 25 module_exit(test_exit); 26 EXPORT_SYMBOL(test_saymyname);
模块B调用模块A的 test_saymyname
1 #include <linux/init.h> 2 #include <linux/module.h> 3 MODULE_LICENSE("Dual BSD/GPL"); 4 5 extern void test_saymyname(void); 6 7 static int __init book_init(void) 8 { 9 test_saymyname(); 10 return 0; 11 } 12 13 static void __exit book_exit(void) 14 { 15 printk(KERN_ALERT"book modules exit\n"); 16 } 17 18 module_init(book_init); 19 module_exit(book_exit);
分别insmod test.ko和insmod book.ko,再卸载模块时,如果先卸载test.ko会发现提示test.ko里面有函数被book.ko调用,无法卸载,只有先卸载book.ko才行。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。