1Linux内核的兼容性
Linux内核自1991年由Linus Torvalds开发问世以来,随着世界各地开发志愿者的不断加入而不停地向前发展,现在很多大公司也加入到其中进行商业开发与运作。Linux的内核功能越来越强,而且可靠性高,新版2.6内核在实时性方面也有很大提高,比起Windows来有着自己显著的特点与优势。
为了保证高可靠与高性能,Linux使用单内核结构,将内核从整体上作为一个大过程实现,并同时运行在一个单独的地址空间。任何的内核服务都在一个地址空间运行,相互之间直接调用函数,简单高效。同时,吸收了微内核的长处——模块化设计,支持动态装载内核模块;还避免了微内核设计上的缺陷,让一切都运行在内核态,直接调用函数,无需消息传递。但由于采用单内核结构,使得它对内核的错误比较敏感。为了避免内核级代码可能给系统带来的危害,Linux对内核代码(如驱动程序)设置了非常严格的检查,以避免兼容性的问题。检查的内容包括:完整的内核版本号(含发行者自定义的部分)、CPU类型、GCC版本号,甚至连函数调用参数传递的方式也需要检查。
如此严格的检查虽然保证了Linux内核稳定、可靠地高速运行,但却给驱动程序开发者带来了麻烦。Linux不像Windows那样具有通用的二进制驱动程序,有时甚至是在不同的内核版本、不同的发行商与不同的GCC下,源程序都有所不同。
下面根据笔者在开发Linux驱动程序时的经验,以Debian4.0发行版为例,尽可能全面地介绍Linux驱动程序兼容性的相关问题。
2Linux驱动开发环境的搭建
就Linux驱动程序开发而言,除了编译器GCC之外,内核源码是必需的,而且源码必须经过正确的编译,安装正确的模块,即通过make(编译)、make modules(模块编译)、make modules_install(安装模块)。
2.1安装内核源码
aptget install linuxtree2.6.18
aptget install linuxkernelheaders
aptget install install linuxheaders`uname r`
内核开发头文件的正确安装是很重要的。如果没有正确安装,则在驱动程序编译时会出现“/lib/modules/`uname r`/build can't exist.”的错误提示。
2.2配置内核
以root身份进入内核源码的目录,执行以下命令:
make mrproper
make menuconfig (如果没有安装ncurses库,此命令将不能被正确执行。可以通过命令aptget install libncurses5dev安装ncurses库之后,再执行此配置命令)
make prepare
make modules_prepare(如果不执行此命令,在编译驱动程序时将会出现“/bin/sh: line 1: scripts/modpost: No such file or directory, in stage 2.”的错误)
3驱动程序的vermagic字符串
3.1vermagic的内容
就2.6版本内核而言,对驱动程序内含的vermagic字符串有着非常严格的检查,没能通过检查的驱动程序是不能加载到内核中去的,即使强制加载也不行。而不同用户的工作开发环境又往往是大不相同的,因此也就具有不同的vermagic。对于驱动程序,如果不是以源码发布的,就需要针对不同的用户环境“定制”相应的驱动程序,使发布的驱动程序二进制版本与用户的环境完全一致。这些内容包括:内核版本号(与所使用的开发内核包相关)、CPU类型等。其中有些vermagic的内容可以通过“make menuconfig”命令来设置,这样就能根据所需要的vermagic的内容,有针对性地对内核进行设置与编译,使它与用户所使用的环境保持一致。一个驱动程序的vermagic内容可以通过modinfo命令得到。例如:
modinfo fan
filename:/lib/modules/2.6.184686/kernel/drivers/acpi/fan.ko
author:Paul Diefenbaugh
description:ACPI Fan Driver
license:GPL
vermagic:2.6.184686 SMP mod_unload 686 REGPARM gcc4.1
depends:
上面显示的内容表示:驱动程序的文件名为/lib/modules/2.6.184686/kernel/drivers/acpi/fan.ko,使用GPL授权模式,作者为Paul Diefenbaugh;需要检查的vermagic字符串的含义是,内核版本号为2.6.184686,CPU的管理模式是SMP,模块加载方式为mod_unload,CPU为686,函数参数传递模式是REGPARM,GCC的版本为4.1。vermagic的这些内容可以在编译内核时进行调整。
3.2vermagic字符串的调整
vermagic字符串由驱动开发所依赖的内核源码决定(并非与Linux宿主机内核一致),可以通过对内核进行配置来设置所需要的vermagic字符串。
在Linux的发展过程中,系统内部在不断地发展,使得驱动程序模块经常出现过时、与新内核特性不兼容而造成系统不稳定的问题。为了解决这一问题,内核执行了非常严格的内核兼容性检查,包括对GCC版本号、CPU类型等信息。如此严格的检查虽然解决了内核驱动模块造成的系统不稳定问题,但是却给驱动程序的二进制兼容带来了麻烦。对于版本号相近的内核,其差别是很小的,如果可以模拟所需要的vermagic字符串,将给实际应用带来很多便利。就目标用户而言,lsmod可以看到很多已经加载的模块名,任意选一个模块名,执行命令modinfo,便可以得到系统要求的vermagic字符串。但有些系统将所有的模块都编译到内核中,没有后来加载的模块,可以在目录“/lib/modules/uname r/”下找到对应的模块名,再使用modinfo命令便可以得到模块的vermagic信息。
下面介绍内核中与vermagic相关的设置。执行make menuconfig,有关的项目内容设置如下:
◆ SMP
Processor type and features ﹥ Symmetric multiprocessor support
◆ CPU type
Processor type and features ﹥ Processor family
◆ Preempt
Processor type and features ﹥ Preemptible kernel
◆ REGPARM
Processor type and features ﹥ Use register arguments
◆ 4K STACKS
Kernel hacking ﹥ Use 4Kb kernel stacks instead of 8Kb
3.3文件vermagic.h
vermagic中,GCC的版本信息可以修改,源码目录的文件include/linux/vermagic.h同时包含了前面所讲的所有关于vermagic字符串的设置内容,用户可以通过直接修改此头文件相关的内容,来达到设置不同的vermagic字符串的目的。用户在修改时需要注意,应该使改动的部分与系统的要求真正兼容,以免当驱动模块插入系统后造成系统崩溃。
#include <linux/utsrelease.h>
#include <linux/module.h>/* Simply sanity version stamp for modules*/
#ifdef CONFIG_SMP/*设置是否定义SMP*/
#define MODULE_VERMAGIC_SMP "SMP "
#else
#define MODULE_VERMAGIC_SMP ""
#endif
#ifdef CONFIG_PREEMPT/*设置是否定义内核为占先式*/
#define MODULE_VERMAGIC_PREEMPT "preempt "
#else
#define MODULE_VERMAGIC_PREEMPT ""
#endif
#ifdef CONFIG_MODULE_UNLOAD/*设置是否允许驱动模块加载*/
#define MODULE_VERMAGIC_MODULE_UNLOAD "mod_unload "
#else
#define MODULE_VERMAGIC_MODULE_UNLOAD ""
#endif
#ifndef MODULE_ARCH_VERMAGIC
#define MODULE_ARCH_VERMAGIC ""
#endif/*根据前面的定义组合成最终的vermagic字符串*/
#define VERMAGIC_STRING
UTS_RELEASE " "
MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT
MODULE_VERMAGIC_MODULE_UNLOAD MODULE_ARCH_VERMAGIC
"gcc-" __stringify(__GNUC__) "." __stringify(__GNUC_MINOR__)
这样最终的vermagic字符串还包含GCC的版本号。
3.4驱动程序的强制安装
由于种种原因,当驱动程序不能通过版本兼容性检查时,可以尝试使用强制加载驱动的方案。虽然这种方案不能完全保证系统的可靠性与稳定性,但是在一些特殊的情况下可以一试。
对于2.4版本内核,可以使用insmodf驱动模块名。但在2.6版本内核下,不能使用此方案,而应该按以下步骤进行:
◆ 复制驱动模块程序到系统库目录“/lib/modules/unamer”中;
◆ modprobe force 驱动模块名(不需要写后缀)。
4未来Linux驱动程序兼容性的解决方案
从前面的讨论可知,Linux通过对驱动程序进行严格的兼容性检查以保证其整个核心的可靠性,但却给普通用户带来了一些不便,难以按二进制的方式发行驱动程序。虽然Windows也采用了一些措施来保证驱动程序的兼容性(如Windows驱动程序的认证),但是没有经过认证的驱动程序,在忽略警告信息后也能够正常安装成功,从而方便了广大用户。如果Linux为了保证内核的可靠性必须采用严格的兼容性检查,还可以参考使用另一种普通用户模式的驱动程序。这种驱动程序工作在低优先权的用户模式下,但可以通过内核程序实现对硬件的操作。这些内核程序是由可靠的专业厂商开发的。Windows下能够使用这种模式进行驱动开发的代表就是Windriver,普通用户开发驱动就像开发一般应用程序一样方便,完全不必担心因为驱动中存在bug而造成系统崩溃。
新版的Linux中也采用了这种用户模式的驱动开发,它主要是针对USB和PCI设备开发了对应的libusb与libpci内核库文件。用户如果想对特定的USB或PCI设备进行驱动开发,只需要调用对应的libusb与libpci库函数功能。整个驱动的开发过程完全可在用户模式下进行,具有很好的安全性与兼容性,这可能是未来普通用户开发Linux驱动程序的解决方案。