分类: LINUX
2010-08-19 19:14:08
尽管Linux是“单核模块”的操作系统——也就是说整个系统内核都运行在一个单独的保护域中,但Linux内核是模块化组成的,它允许内核在运行时动态 的向其中插入或从中删除代码。这些代码——包括相关的子例程、数据、函数入口和函数出口被一并组合在一个单独的二进制镜像中,即所谓的可装载内核模块中, 或被简称为模块。
Hello World!
与开发我们讨论过的大多数内核子系统不同,模块开发更接近编写新的应用系统,因为至少要在模块文件中具有入口点和出口点。虽然编写“hello world ”程序作为实例实属陈词滥调了,但它却是讨人喜欢:
#include
#include
#include
static int hello_init(void)
{ printk(KERN_ALERT " I bear a charmed life. \n")'
return 0;
}
static void hello_exit(void)
{ printk(KERN_ALERT "out, out, brief candle ! \n"); }
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shakespeare");
这大概是我们能见到的最简单的内核模块了,hello_init()函数是模块的入口点,它通过module_init()例程注册到系统中,在模 块装载时被调用。调用module_init()实际上是一个宏调用,它唯一的参数是模块的初始化函数。模块的所有初始化函数都必须符合下面的形式:
int my_init(void);
init函数会返回一个int型数值,如果初始化顺利完成,那么返回0,否则返回一个非0值。
Hello_exit()函数是模块的出口函数,它由module_exit()例程注册到系统。在模块从内存卸载时,内核便会调用 hello_exit()。退出函数可能会在返回前负责清理资源,以保证硬件处于一致状态,或者做其他别的一些工作。退出函数返回后,模块卸载完毕。
16.1 构建模块
16.1.1 放在内核源代码树中
最理想的情况莫过于你的模块正式成为Linux内核的一部分,这样就会被存放在内核源代码树中。
假定有一个字符设备,而且希望将它放在driver/char/目录下,那么要注意,在该目录下同时会存在大量的C源代码文件和许多2其他目录。所以对于 仅仅只有一两个源文件的设备驱动程序,可以直接存放在该目录下,但如果驱动程序包含许多源文件和其它辅助文件,那么可以创建一个新子目录。假设你想建立自 己代码的子目录,你的驱动程序是一个钓鱼竿和计算机的接口,那么应在/drive/char/目录下建立一个名为fishing的子目录。现在需要向 driver/char/下的Makefile文件中添加一行。
obj-m += fishing/
这行编译指令告诉模块构建系统在编译模块时需要进入fishing/子目录中。更可能发生的情况是你的驱动程序的编译取决于一个特殊的配置选项,比如CONFIG_FISHING_POLE。此时用下面的指令:
obj-$(CONFIG_FISHING_POLE) += fishing/
最后,还要在fishing/下添加一个新的Makefile文件,其中要有下面命令:
obj-m += fishing.o
一起就绪!此刻构建系统运行就将会进入fishing/目录下,并且将fishing编译为fishing.ko模块。
以后,加入你的钓鱼竿驱动程序需要更加智能化——它可以自动检测钓鱼线。这是驱动程序源文件可能就不止一个了。此时你只要你的Makefile作如下修改就可以搞定:
obj-$(CONFIG_FISHING_POLE) += fishing.o
fishing-objs := fishing-main.o fishing-line.o
这样fishing-main.c和fish-line.c就一起被编译和连接到了fishing.ko模块内。
16.1.2 放在内核代码外
如果你喜欢脱离内核代码树来维护和构建你的模块,把自己作为一个圈外人,那你要做的就是在你自己的源代码树目录建立一个Makefile文件,它只需要一行指令:
obj-m := fishing.o
就可以把fishing.c编译成fishing.ko。
模块在内核内或内核外构建的最大区别在于构建过程。当你的模块在内核源代码树的外围时,你必须告诉make如何找到内核源代码文件和基础Makefile文件。要完成这个工作同样不难:
make -C /kernel/source/location SUBDIRS=$PWD modules
/kernel/source/location是你已配置的内核源码树。
安装模块 make module_install 通常需要以root权限运行。
产生模块依赖性:Linux模块之间存在依赖性,也就是说钓鱼模块依赖于鱼饵模块,那么当载入钓鱼模块时,鱼饵模块会被自动载入。若想产生内核依赖关系的信息,root用户可以运行命令:
depmod
为了执行更快的更新操作,可以只为新模块生成依赖信息,而非生成所有的依赖关系:
depmod -A
模块依赖关系信息存放在/lib/module/version/modules.dep文件中。
16.2 载入模块
载入模块最简单的方法是通过insmod命令。这是个功能很有限的命令,它的作用就是请求内核载入你指定的模块。卸载模块用rmmod命令。
我们要重点说明的是一个更为先进的工具modprobe,它提供了模块依赖性分析,错误智能检查,错误报告以及许多其他功能和选项。
modprobe module [module parameters]
modprobe命令也可以用来从内核中卸载模块,以root身份登录。
modprobe -r modules
与rmmod命令不同,modprobe也会卸载给定模块所依赖的相关模块,其前提是这些相关模块没有被使用。
16.3 管理配置选项
16.4 模块参数
Linux提供了这样一个简单框架——它可以允许驱动程序声明参数,从而用户可以在系统启动或者模块装载时再指定参数值,这些参数对于驱动程序而言属于全局变量。
定义一个模块参数可通过宏module_param()完成:
module_param(name, type, perm);
参数name既是用户可见的参数名,也是模块中存放模块参数的变量名。参数type则存放了参数的类型,最后一个参数perm指定了模块在sysfs文件 系统下对应文件的权限。该值可以是8进制的格式,或者是S_Ifoo的定义形式,如S_IRUGO | S_IWUSR,如果该值为0,表示禁止所有的sysfs项。
上面的值宏并没有定义变量,你必须在使用该宏前进行变量定义。通常使用类似下面的语句完成定义。
static int allow_live_bait = 1; /* 默认功能允许 */
module_param(allow_live_bait, bool, 0644); /* 一个Boolean类型 */
这个值处于模块代码文件之外,换句话说,allow_live_bait是个全局变量。
有可能模块的外部参数名称不同于它对应的内部变量名称,这时就该使用宏module_param_named()定义了:
module_param_named(name, variable, type, perm);
name是外部可见的参数名称,参数variable是参数对应的内部变量名称。
通常,需要用一个charp类型来定义模块参数,内核将用户提供的这个字符串拷贝到内存,而且将你的变量指向该字符串。比如:
static char *name;
module_param(name, charp, 0);
如果需要,也可以使内核直接拷贝字符串到你指定的字符串数组。宏module_param_string()可完成上述任务: module_param_string(name, string, len, perm);
这里参数name为外部参数名称,参数string是对应的内部变量名称,参数len是sting命名缓冲区的长度。
16.5 导出符号表
模块被载入后,就会动态连接到内核。注意,它与用户空间中的动态链接库类似,只有当被显式导出后的外部函数,才可以被动态调用。在内核中,导出内核函数需要使用特殊命令:EXPORT_SYSMBOL()和EXPORT_SYMBOL_GPL()。
导出后的内核函数可以被模块调用,而未导出的函数则无法被模块所调用。导出的内核符号表被看作是导出的内核接口,甚至称为内核API。导出符号很简单,在声明函数后,紧跟上EXPORT_SYMBOL()指令就搞定了。
如果你的代码被配置为模块,那么就必须确保当它被编译为模块时所用到的全部接口都已被导出,否则就会产生连接错误。