快捷搜索:

Linux内核可装载模块对设备驱动的影响

从 2.4 到 2.6,Linux 内核在可装载模块机制、设备模型、一些核心 API 等方面发生较大年夜改变,设备驱动开拓职员面临着将驱动从 2.4 移植到 2.6 内核,或是使驱动同时支持2.4 与 2.6 内核的义务。站在设备驱动开拓职员的角度,驱动由一个或几个外部可加载内核模块组成,本文针对 2.6 内核里模块机制的改变对编写设备驱动法度榜样的影响,从内核模块的编译、装载时的版本反省、初始化与退出、模块应用计数、输出内核符号、敕令行输入参数、许可证声明等方面对照了 2.4 与 2.6 内核的差别;并总结了使设备驱动同时支持 2.4 与 2.6 内核的一系列模板。

1.获取内核版本

当设备驱动必要同时支持不合版本内核时,在编译阶段,内核模块必要知道当前应用的内核源码的版本,从而应用响应的内核 API。2.4 与 2.6 内核下,源码头文件 linux/version.h 定义有:

LINUX_VERSION_CODE ― 内核版本的二进制表示,主、从、修订版本号各对应一个字节;

KERNEL_VERSION(major, minor, release) - 由主、从、修订版本号构造二进制版本号。

在同时支持2.4与2.6 内核的设备驱动法度榜样中,常常可以看到以下代码段:

清单1:判断内核版本的代码段。

/*code in 2.6 kernel*/ /*code in 2.4 kernel */

2.内核模块机制的改变

2.1模块编译

从2.4到2.6,外部可装载内核模块的编译、连接历程以及Makefile的书写都发生了改变。

2.4内核中,模块的编译只需内核源码头文件;必要在包孕linux/modules.h之前定义MODULE;编译、连接后天生的内核模块后缀为.o。

2.6内核中,模块的编译必要设置设置设备摆设摆设过的内核源码;编译、连接后天生的内核模块后缀为.ko;编译历程首先会到内核源码目录下,读取顶层的Makefile文件,然后再返回模块源码所在目录。

清单2:2.4 内核模块的Makefile模板

#Makefile2.4 KVER=$(shell uname -r) KDIR=/lib/modules/$(KVER)/build OBJS=mymodule.o CFLAGS=-DKERNEL -I$(KDIR)/include -DMODULE -DKERNEL_SYSCALLS -DEXPORT_SYMTAB -O2 -fomit--pointer -Wall -DMODVERSIONS -include $(KDIR)/include/linux/modversions.h

all: $(OBJS) mymodule.o: file1.o file2.o ld -r -o $@ $^ clean: rm -f *.o

在2.4 内核下,内核模块的Makefile与通俗用户法度榜样的Makefile在布局和语法上都相同,然则必须在CFLAGS中定义-DKERNEL- DMODULE,指定内核头文件目录-I$(KDIR)/include。有一点需留意,之以是在CFLAGS中定义变量,而不是在模块源码文件中定义,一方面这些预定义变量可以被模块中所有源码文件可见,另一方面等价于将这些预定义变量定义在源码文件的肇端位置。在模块编译中,对付这些全局的预定义变量,一样平常在CFLAGS中定义。

清单3:2.6 内核模块的Makefile模板

# Makefile2.6 ifneq ($(KERNELRELEASE),) mymodule-objs := file1.o file2.o obj-m := mymodule.o else PWD := $(shell pwd) KVER ?= $(shell uname -r) KDIR := /lib/modules/$(KVER)/build all: $(MAKE) -C $(KDIR) M=$(PWD) clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions endif

KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取履行此Makefile时, KERNELRELEASE没有被定义,以是make将读取履行else之后的内容。假如make的目标是clean,直接履行clean操作,然后停止。当make的目标为all时,-C $(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 注解然后返回到当前目录继承读入、履行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继承读取else之前的内容。else之前的内容为kbuild语法的语句, 指明模块源码中各文件的依附关系,以及要天生的目标模块名。mymodule-objs := file1.o file2.o表示mymoudule.o 由file1.o与file2.o 连接天生。obj-m := mymodule.o表示编译连接后将天生mymodule.o模块。

为内核符号添加校验字符串来验证模块的版本与内核的版本是否匹配是复杂和挥霍内核空间的;而且跟着SMP(对称多处置惩罚器)、PREEMPT(可抢占内核)等机制在2.6内核的引入和完善,模块运行时对内核的依附不仅取决于内核版本,还取决于内核的设置设置设备摆设摆设,此时内核符号的校验码是否同等不能成为判断模块可否被加载的充分前提。2.6 内核下,在linux/vermagic.h中定义有VERMAGIC_STRING,VERMAGIC_STRING不仅包孕内核版本号,还包孕有内核应用的gcc版本,SMP与PREEMPT等设置设置设备摆设摆设信息。模块在编译时,我们可以看到屏幕上会显示“MODPOST”。在此阶段, VERMAGIC_STRING会添加到模块的modinfo段。在内核源码目录下smodmodpost.c文件中可以看到模块后续处置惩罚部分的代码。模块编译天生后,经由过程`modinfo mymodule.ko`敕令可以查看此模块的vermagic等信息。2.6 内核下的模块装载器里保存有内核的版本信息,在装载模块时,装载器会对照所保存的内核vermagic与此模块的modinfo段里保存的 vermagic信息是否同等,两者同等时,模块才能被装载。譬如Fedora core 4 与core 2 应用的都是2.6 版本内核,在Fedore Core 2下去加载Fedora Core4下编译天生的hello.ko,会呈现“invalid module format” 差错。

#insmod hello.ko Invalid module format hello: version magic ‘2.6.11-1.1369_FC4 686 REGPARM 4KSTACKS gcc-4.0’ should be ‘2.6.5-1.358 686 REGPARM 4KSTACKS gcc-3.3’

2.3模块的初始化与退出

在2.6内核中,内核模块必须调用宏module_init 与module_exit() 去注册初始化与退出函数。在2.4 内核中,假如初始化函数命名为init_module()、退出函数命名为cleanup_module(),可以不必应用module_init 与module_exit 宏。保举应用module_init 与module_exit宏,使代码在2.4与2.6内核中都能事情。

2.6内核下,对付为详细设备写驱动的开拓职员而言,基础无需应用try_module_get与module_put,由于此时开拓职员所写的驱动平日为支持某详细设备的owner模块,对此设备owner模块的计数治来由内核里更底层的代码如总线驱动或是此类设备共用的核心模块来实现,从而简化了设备驱动开拓。

2.5 模块输出的内核符号

2.4 内核下,缺省环境时模块中的非静态全局变量及函数在模块加载后会输出到内核空间。

2.6 内核下,缺省环境时模块中的非静态全局变量及函数在模块加载后不会输出到内核空间,必要显式调用宏EXPORT_SYMBOL才能输出。以是在2.6 内核的模块下,EXPORT_NO_SYMBOLS宏的调用没故意义,是空操作。在同时支持2.4与2.6内核的设备驱动中,可以经由过程以下代码段来输出模块的内核符号

清单6: 同时支持2.4与2.6的输出内核符号代码段

EXPORT_NO_SYMBOLS; EXPORT_SYMBOL(var); EXPORT_SYMBOL(func);

必要留意的是如需在2.4内核下应用 EXPORT_SYMBOL,必须在 CFLAGS中定义 EXPORT_SYMTAB,否则编译将会掉败。

从优越的代码风格角度启程,模块中不必要输出到内核空间且不需为模块中其它文件所用的全局变量及函数最好显式申明为static类型,必要输出的内核符号以模块名为前缀。

模块加载后,2.4内核下可经由过程 /proc/ksyms、 2.6 内核下可经由过程/proc/kallsyms查看模块输出的内核符号

2.6 模块的敕令行输入参数

在装载内核模块时,用户可以向模块通报一些参数,如`modprobe modname var=`,否则,var将应用模块内定义的缺省值。

2.4内核下,linux/module.h中定义有宏MODULE_PARM(var,type) 用于向模块通报敕令行参数。var为吸收参数值的变量名,type为采取如下款式的字符串[min[-max]]{b,h,i,l,s}。min及max 用于表示当参数为数组类型时,容许输入的数组元素的个数范围;b:byte;h:short;i:int;l:long;s:string。

模块编译天生后,加载模块时可以输入:`modprobe my_module mode=1024x768 debug=1 tuner=22,33`。

在linux/moduleparam.h还定义有:

module_param_array_named(name, array, type, nump, perm) module_param_call(name, set, get, arg, perm) module_param_named(name, , type, perm)

读者可以参阅linux/moduleparam.h查看这些宏的具体描述,有一点需留意,在2.6内核里,module_param这一系列宏应用的都是小写名字。

2.7 模块的许可证声明

从2.4.10版本内核开始,模块必须经由过程MODULE_LICENSE宏声明此模块的许可证,否则在加载此模块时,会收到内核被污染 “kernel tainted” 的警告。从linux/module.h文件中可以看到,被内核吸收的故意义的许可证有 “GPL”,“GPL v2”,“GPL and additional rights”,“Dual BSD/GPL”,“Dual MPL/GPL”,“Proprietary”。

在同时支持2.4与2.6内核的设备驱动中,模块可按如下要领声明自己的许可证。

清单8: 适用于2.4与2.6内核的模块许可证声明模板

MODULE_LICENSE(“GPL”);

小结

此外,2.6内核里还有一些模块机制的改变,不常为驱动开拓职员用到。如加载内核模块的接口request_module在2.4 下为request_module(const char * module_name);在2.6内核下为request_module(const char *fmt, …)。在2.6 内核下,驱动开拓职员可以经由过程调用

request_module(“msp3400”); request_module(“char-major-%d-%d”, MAJOR(dev), MINOR(dev));

这种更机动的要领加载其它内核模块。

2.6内核在linux/module.h中还供给了MODULE_ALIAS(alias)宏,模块可以经由过程调用此宏为自己定义一或多少个别称。而在2.4内核下,用户只能在/etc/modules.conf中为模块定义别称。

经由过程以上对照可以看到,从2.4到2.6内核,可装载模块治理机制的改变使设备驱动的开拓变得加倍简洁、机动、壮实。

您可能还会对下面的文章感兴趣: