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);

   这个驱动什么都没干,就打印了几句话,但是具备了驱动程序最基本的几个要素:

  1.  驱动初始化函数 XXX_init(void),使用宏module_init(test_init);告诉内核这是模块的初始化函数入口。
  2.  驱动退出函数XXX_exot(void),使用宏module_exit(test_exit);告诉内核这是模块的退出函数入口。
  3.    驱动模块遵循的协议 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才行。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。