Linux驱动之内核模块
一、Linux内核模块简介
1.1 Linux内核模块介绍
一种方法是把所有需要的功能都编译到Linux内核。这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。
有没有一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中呢?
•++模块本身不被编译如内核映像,从而控制了内核的大小++;
•++模块一旦被加载,它就和内核中的其他部分完全一样++;
模块化的最大的好处是可以动态扩展应用程序的功能而无需重新编译链接生成一个新的应用程序映像(或内核)。这对应Windows系统上为动态链接库DDL(Dynamic Link Library),对应到Linux系统中为驱动模块和应用程序的共享库so(shared object)。
模块的机制使驱动程序与Linux内核结合有两种方式:在编译内核时,静态地链接进内核;++在系统运行时,以模块加载的方式加载进内核++。
1.2 Linux内核模块的构成
一个Linux内核模块主要由如下几个部分组成:
(1)模块加载函数(一般需要)
当通过insmod或modprobe命令加载内核模块时,模块的加载函数会被内核执行,完成本模块的相关初始化工作。
(2)模块卸载函数(一般需要)
当通过rmmod命令卸载模块时,模块的卸载函数会自动被内核执行,完成与加载函数相反的功能。
printf输出格式linux(3)模块许可证声明(必须)
许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将收到内核被污染(kernel tainted)的警告。
在Linux 2.6内核中,可接受的LICENSE包括“GPL”、“GPL v2”、“GPL and additional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”。大多数情况下,内核模块应遵循GPL兼容许可权,Linux 2.6内核模块最常见的是以MODULE_LICENSE(“Dual BSD/GPL”);语句声明模块采用BSD/GPL双LICENSE。
(4)模块参数(可选)
模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。
(5)模块导出符号(可选)
内核模块可以导出符号(symbol,对应于函数或变量),这样其他模块就可以使用本模块中的变量或函数。
(6)模块作者等信息声明(可选)
1.3 一个简单的Linux内核模块
一个基本的内核模块只需包含头文件、模块加载函数、模块卸载函数以及许可协议和一些描述信息。如代码清单1.1所示。
代码清单1.1 一个最简单的Linux内核模块
编译它会产生一个hello.ko目标文件,通过“insmod ./hello.ko”命令可以加载它,通过“rmmod hello”命令可以卸载它,加载时输出“Hello world enter.”,卸载时输出“Hello world exit”。
注释:内核模块中的输出函数是内核空间的printk(而非用户空间的printf(,printk(用法与printf(基本相似,但前者可以定义输出级别。printk(可作为一种最基本的内核调试手段,在Linux驱动的调试章节中将详细讲解这个函数。
内核中已加载模块的信息存在于/sys/module目录下,加载hello.ko后,内核中将包含/sys/module/hello目录,该目录下又包含一个refcnt文件和一个sections目录,在/sys/module/hello目录下运行“tree -a”得到如下目录树:
# tree -a . , -- holders , -- initstate , -- notes ,  `-- .u.build-id , -- refcnt , -- sections ,  , -- .bss ,  , -- .data ,  , -- .gnu.linkonce.this_module ,  , -- .u.build-id ,  , -- .rodata.str1.1 ,  , -- .strtab ,  , -- .symtab ,  , -- .text ` -- srcversion     3 directories, 12 files
1.4 Linux模块操作常用命令
在编写、调试和使用Linux内核模块的过程中,常常会使用到lsmod、modprobe、rmmod和modinfo命令。
++lsmod命令可以获得系统中加载了的所有模块以及模块间的依赖关系++,例如:
Module          Size  Used  by hello            9      472    0 nls_iso8859_1    12    032    1 nls_cp437        13    696    1 vfat            18    816    1 fat              57    376    1  vfat …
lsmod命令实际上是读取并分析“/proc/modules”文件,与上述lsmod命令结果对应的“/proc/modules”文件如下:
# cat /proc/modules hello 9472 0 - Live 0xf953b000 nls_iso8859_1 12032 1 - Live 0xf950c000 nls_cp437 13696 1 - Live 0xf9561000 vfat 18816 1 - Live 0xf94f3000 …
++modprobe命令比insmod命令要强大,它在加载模块时,会同时加载该模块所依赖的其他模块。使用modprobe命令加载的模块若以“modprobe -r filename”的方式卸载将同时卸载其依赖的模块。++
++使用modinfo<模块名>命令可以获得模块的信息,包括模块作者、模块说明、模块所支持的参数以及vermagic++。
二、Linux内核模块详解
2.1 模块加载函数
++Linux内核模块加载函数一般以__init标识声明++,典型的模块加载函数的形式如代码清单1.2所示。
代码清单1.2 内核模块加载函数
static int __init initialization_function(void) {    /* 初始化代码 */ } 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函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数,并在初始化完成后,释放init区段(包括.、initcall.init等)。
2.2 模块卸载函数
++Linux内核模块卸载函数一般以__exit标识声明++,典型的模块卸载函数的形式如代码清单1.3所示。
代码清单1.3 内核模块卸载函数
static void __exit cleanup_function(void) {    /* 释放代码 */ } module_exit(cleanup_function);
模块卸载函数在模块卸载的时候执行,不返回任何值,++必须以“module_exit(函数名)”的形式来指定++。
通常来说,模块卸载函数要完成与模块加载函数相反的功能,如下所示:
•若模块加载函数注册了xxx,则模块卸载函数应该注销xxx;
•若模块加载函数动态申请了内存,则模块卸载函数应释放该内存;
•若模块加载函数开启了硬件,则模块卸载函数中一般要关闭该硬件;
•若模块加载函数申请了硬件资源(中断、DMA通道、I/O端口和I/O内存等)的占用,则模块卸载函数应释放这些硬件资源;
和__init一样,__exit也可以使对应的函数在运行完成后自动回收内存。实际上,__init和__exit都是宏,其定义分别为:
#define __init  __attribute__ ((__section__(“.”))) #define MODULE #define __exit  __attribute__ ((__section__(“.”))) #else #define __exit  __attribute_used__attribute__ ((__section__(“.”))) #endif
数据也可以被定义为__initdata和__exitdata,这两个宏分别为:
#define __initdata  __attribute__ ((__section__(“.init.data”))) #define __exitdata  __attribute__ ((__section__(“.exit.data”)))
2.3 模块参数
我们可以++用“module_param(参数名,参数类型,参数读/写权限)”为模块定义一个参数++,例如下列代码定义了1个整型参数和1个字符指针参数:
static char *book_name = “dissecting Linux Device Driver”; static int num = 4000; module_param(num, int, S_IRUGO); module_param(book_name, charp, S_IRUGO);

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。