Linux内核模块实例

来源:互联网 发布:java中的构造器是什么 编辑:程序博客网 时间:2024/06/06 18:44

http://blog.csdn.net/xinyuwuxian/article/details/9274895

Linux 内核模块加载函数一般以_ _init 标识声明,典型的模块加载函数的形式如代码所示: 

[html] view plaincopy
  1. 1   static int _ _init initialization_function(void)   
  2. 2   {     
  3. 3   /* 初始化代码 */   
  4. 4   }   
  5. 5   module_init(initialization_function);  
模块加载函数必须以“module_init(函数名)”的形式被指定。它返回整型值,若初始化成功,应返回 0。而在初始化失败时,应该返回错误编码。在 Linux 内核里,错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM 之类的符号值。返
回相应的错误编码是种非常好的习惯,因为只有这样,用户程序才可以利用 perror 等方法把它们转换成有意义的错误信息字符串。 

在 Linux 2.6 内核中,可以使用 request_module(const char *fmt, …)函数加载内核模块,驱动开发人员可以通过调用 

request_module(module_name); 

或 

request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev)); 

来加载其他内核模块。 

在 Linux 内核中,所有标识为_ _init 的函数在连接的时候都放在.init.text 这个区段内,此外,所有的_ _init 函数在区段.initcall.init 中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些_ _init 函数,并在初始化完成后释放 init 区段(包括.init.text,.initcall.init 等)。 

模块卸载函数 

Linux 内核模块卸载函数一般以_ _exit 标识声明,典型的模块卸载函数的形式如代码所示:

[html] view plaincopy
  1. 1  static void _ _exit cleanup_function(void)   
  2. 2  {   
  3. 3  /* 释放代码 */   
  4. 4  }   
  5. 5  module_exit(cleanup_function);   
模块卸载函数在模块卸载的时候执行,不返回任何值,必须以“module_exit(函数名)”的形式来指定。 

通常来说,模块卸载函数要完成与模块加载函数相反的功能,如下所示。 

若模块加载函数注册了 XXX,则模块卸载函数应该注销 XXX。 

若模块加载函数动态申请了内存,则模块卸载函数应释放该内存。 

若模块加载函数申请了硬件资源(中断、DMA 通道、I/O 端口和 I/O 内存等)的占用,则模块卸载函数应释放这些硬件资源。 

若模块加载函数开启了硬件,则卸载函数中一般要关闭硬件。 

和_ _init 一样,_ _exit 也可以使对应函数在运行完成后自动回收内存。实际上,_ _init 和_ _exit 都是宏,其定义分别为: 

[html] view plaincopy
  1. #define _ _init _ _attribute_ _ ((_ _section_ _ (".init.text")))  

[html] view plaincopy
  1. #ifdef MODULE   
  2. #define _ _exit _ _attribute_ _ ((_ _section_ _(".exit.text")))   
  3. #else   
  4. #define _ _exit _  _attribute_used_  _  _  _attribute_  _  ((_  _section_ _(".exit.text")))   
  5. #endif   
数据也可以被定义为_initdata 和_exitdata,这两个宏分别为: 
[html] view plaincopy
  1. #define _ _initdata_ _attribute_ _ ((_ _section_ _ (".init.data")))   

[html] view plaincopy
  1. #define _ _exitdata_ _attribute_ _ ((_ _section_ _(".exit.data")))  

模块参数 

我们可以用“module_param(参数名,参数类型,参数读/写权限)”为模块定义一个参数,例如下列代码定义了一个整型参数和一个字符指针参数:

[html] view plaincopy
  1. static char *book_name = "深入浅出 Linux 设备驱动";   
  2. static int num = 4000;   
  3. module_param(num, int, S_IRUGO);   
  4. module_param(book_name, charp, S_IRUGO);   
在装载内核模块时,用户可以向模块传递参数,形式为“insmode(或 modprobe)模块名 参数名=参数值”,如果不传递,参数将使用模块内定义的默认值。 

参数类型可以是 byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool 或 invbool(布尔的反),在模块被编译时会将 module_param 中声明的类型与变量定义的类型进行比较,判断是否一致。 

模块被加载后,在/sys/module/目录下将出现以此模块名命名的目录。当“参数读/写权限”为 0 时,表示此参数不存在 sysfs 文件系统下对应的文件节点,如果此模块存在“参数读/写权限”不为 0 的命令行参数,在此模块的目录下还将出现 parameters目录 ,包含一系列以 参 数名 命名 的 文件 节点 ,这 些 文件 的 权限 值就是传入module_param()的“参数读/写权限”,而文件的内容为参数的值。 

除此之外,模块也可以拥有参数数组,形式为“module_param_array(数组名,数组类型,数组长,参数读/写权限)”。从 2.6.0~2.6.10 版本,需将数组长变量名赋给“数组长”,从 2.6.10 版本开始,需将数组长变量的指针赋给“数组长”,当不需要保存实际输入的数组元素个数时,可以设置“数组长”为 NULL。 运行 insmod 或 modprobe 命令时,应使用逗号分隔输入的数组元素。 

现在我们定义一个包含两个参数的模块(如代码所示),并观察模块加载时被传递参数和不传递参数时的输出。

[html] view plaincopy
  1. 1  #include <linux/init.h>       
  2. 2  #include <linux/module.h>    
  3. 3  MODULE_LICENSE("Dual BSD/GPL");                                   
  4. 4     
  5. 5  static char *book_name = "dissecting Linux Device Driver";        
  6. 6  static int num = 4000;       
  7. 7           
  8. 8  static int book_init(void)              
  9. 9  {                                   
  10. 10  printk(KERN_INFO " book name:%s\n",book_name);                           
  11. 11  printk(KERN_INFO " book num:%d\n",num);                                  
  12. 12  return 0;                                   
  13. 13 }                                   
  14. 14 static void book_exit(void)                                   
  15. 15 {                                   
  16. 16  printk(KERN_ALERT " Book module exit\n ");                               
  17. 17 }                                   
  18. 18 module_init(book_init);                                   
  19. 19 module_exit(book_exit);                                   
  20. 20 module_param(num, int, S_IRUGO);                                   
  21. 21 module_param(book_name, charp, S_IRUGO);   
  22. 22                                    
  23. 23 MODULE_AUTHOR("Song Baohua, author@linuxdriver.cn");   
  24. 24 MODULE_DESCRIPTION("A simple Module for testing module params");   
  25. 25 MODULE_VERSION("V1.0");   
对上述模块运行“insmod book.ko”命令加载,相应输出都为模块内的默认值,通过查看“/var/log/messages”日志文件可以看到内核的输出,如下所示:
[html] view plaincopy
  1. [root@localhost driver_study]# tail -n 2 /var/log/messages   
  2. Jul  2 01:03:10 localhost kernel:  <6> book name:dissecting Linux Device Driver   
  3. Jul  2 01:03:10 localhost kernel:  book num:4000  
当用户运行“insmod book.ko book_name='GoodBook' num=5000”命令时,输出的是用户传递的参数,如下所示:
[html] view plaincopy
  1. [root@localhost driver_study]# tail -n 2 /var/log/messages   
  2. Jul  2 01:06:21 localhost kernel:  <6> book name:GoodBook   
  3. Jul  2 01:06:21 localhost kernel:  book num:5000   

导出符号 

Linux 2.6 的“/proc/kallsyms”文件对应着内核符号表,它记录了符号以及符号所在的内存地址。 

模块可以使用如下宏导出符号到内核符号表: 

EXPORT_SYMBOL(符号名); 

EXPORT_SYMBOL_GPL(符号名); 

导 出 的 符 号 将 可 以 被 其 他 模 块 使 用 , 使 用 前 声 明 一 下 即 可 。

EXPORT_SYMBOL_GPL()只适用于包含 GPL 许可权的模块。代码清单给出了一个导出整数加、减运算函数符号的内核模块的例子(这些导出符号没有实际意义,只是为了演示)。

[html] view plaincopy
  1. 1  #include <linux/init.h>                                   
  2. 2  #include <linux/module.h>                                   
  3. 3  MODULE_LICENSE("Dual BSD/GPL");                                   
  4. 4                                     
  5. 5  int add_integar(int a,int b)                                   
  6. 6  {                                   
  7. 7   return a+b;                                
  8. 8  }    
  9. 9                                    
  10. 10 int sub_integar(int a,int b)                                   
  11. 11 {                                   
  12. 12  return a-b;                                
  13. 13 }                               
  14. 14    
  15. 15 EXPORT_SYMBOL(add_integar);   
  16. 16 EXPORT_SYMBOL(sub_integar);   
从“/proc/kallsyms”文件中找出 add_integar、sub_integar 相关信息:
[html] view plaincopy
  1. [root@localhost driver_study]# cat /proc/kallsyms | grep integar   
  2. c886f050 r _ _kcrctab_add_integar        [export]   
  3. c886f058 r _ _kstrtab_add_integar        [export]   
  4. c886f070 r _ _ksymtab_add_integar        [export]   
  5. c886f054 r _ _kcrctab_sub_integar        [export]   
  6. c886f064 r _ _kstrtab_sub_integar        [export]   
  7. c886f078 r _ _ksymtab_sub_integar        [export]   
  8. c886f000 T add_integar  [export]   
  9. c886f00b T sub_integar  [export]   
  10. 13db98c9 a _ _crc_sub_integar    [export]   
  11. e1626dee a _ _crc_add_integar    [export]   

模块声明与描述 

在 Linux 内核模块中,我们可以用 MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_ VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS 分别声明模块的作者、描述、版本、设备表和别名,例如: 

MODULE_AUTHOR(author); 

MODULE_DESCRIPTION(description); 

MODULE_VERSION(version_string); 

MODULE_DEVICE_TABLE(table_info); 

MODULE_ALIAS(alternate_name); 

对于 USB、PCI 等设备驱动,通常会创建一个 MODULE_DEVICE_TABLE,如代码所示:

[html] view plaincopy
  1. 1 /* 对应此驱动的设备表 */    
  2. 2 static struct usb_device_id skel_table [] = {    
  3. 3 { USB_DEVICE(USB_SKEL_VENDOR_ID,    
  4. 4    USB_SKEL_PRODUCT_ID) },    
  5. 5   { } /* 表结束 */    
  6. 6 };    
  7. 7    
  8. 8 MODULE_DEVICE_TABLE (usb, skel_table);   
此时,并不需要读者理解 MODULE_DEVICE_TABLE 的作用,后续相关章节会有详细介绍。 

模块的使用计数 

Linux 2.4 内 核 中 , 模 块 自 身 通 过 MOD_INC_USE_COUNT 、MOD_DEC_USE_COUNT 宏来管理自己被使用的计数。 

Linux 2.6 内核提供了模块计数管理接口 try_module_get(&module)和 module_put (&module),从而取代 Linux 2.4 内核中的模块使用计数管理宏。模块的使用计数一般不必由模块自身管理,而且模块计数管理还考虑了 SMP 与 PREEMPT 机制的影响。

int try_module_get(struct module *module); 

该函数用于增加模块使用计数;若返回为 0,表示调用失败,希望使用的模块没有被加载或正在被卸载中。 

void module_put(struct module *module); 

该函数用于减少模块使用计数。 

try_module_get ()与 module_put()的引入与使用与 Linux 2.6 内核下的设备模型密切相关。Linux 2.6 内核为不同类型的设备定义了 struct module *owner 域,用来指向管理此设备的模块。当开始使用某个设备时,内核使用 try_module_get(dev->owner)
去增加管理此设备的 owner 模块的使用计数;当不再使用此设备时,内核使用module_put(dev->owner)减少对管理此设备的 owner 模块的使用计数。这样,当设备在使用时,管理此设备的模块将不能被卸载。只有当设备不再被使用时,模块才允许被
卸载。 

在Linux 2.6内核下,对于设备驱动工程师而言,很少需要亲自调用try_module_get()与 module_put(),因为此时开发人员所写的驱动通常为支持某具体设备的 owner 模块,对此设备 owner 模块的计数管理由内核里更底层的代码(如总线驱动或是此类设备共用的核心模块)来实现,从而简化了设备驱动的开发。

模块的编译 

我们可以为hello的模板编写一个简单的 Makefile,如下所示: 

obj-m := hello.o 

并使用如下命令编译 Hello World 模块,如下所示: 

make -C /usr/src/linux-2.6.15.5/ M=/driver_study/ modules 

如果当前处于模块所在的目录,则以下命令与上述命令同等: 

make – C /usr/src/linux-2.6.15.5 M=$(pwd) modules 

其中-C 后指定的是 Linux 内核源代码的目录,而 M=后指定的是 hello.c 和 Makefile

所在的目录,编译结果如下所示:

[html] view plaincopy
  1. [root@localhost  driver_study]#  make  -C  /usr/src/linux-2.6.15.5/ M=/driver_study/ modules   
  2. make: Entering directory '/usr/src/linux-2.6.15.5'   
  3.   CC [M]  /driver_study/hello.o   
  4. /driver_study/hello.c:18:35: warning: no newline at end of file   
  5.   Building modules, stage 2.   
  6.   MODPOST   
  7.   CC      /driver_study/hello.mod.o   
  8.   LD [M]  /driver_study/hello.ko   
  9. make: Leaving directory '/usr/src/linux-2.6.15.5'   
从中可以看出,编译过程中经历了这样的步骤:先进入 Linux 内核所在的目录,并编译出 hello.o 文件,运行 MODPOST 会生成临时的 hello.mod.c 文件,而后根据此文件编译出 hello.mod.o,之后连接 hello.o 和 hello.mod.o 文件得到模块目标文件
hello.ko,最后离开 Linux 内核所在的目录。 中间生成的 hello.mod.c 文件的源代码如下所示:
[html] view plaincopy
  1. 1   #include <linux/module.h>   
  2. 2   #include <linux/vermagic.h>   
  3. 3   #include <linux/compiler.h>   
  4. 4      
  5. 5   MODULE_INFO(vermagic, VERMAGIC_STRING);   
  6. 6      
  7. 7   struct mo

0 0
原创粉丝点击