升级Linux硬件驱动结构
升级Linux硬件驱动结构
--------------------------------------------------------------------------------
作者:冯立功编译 2005-01-18 10:55:19 来自:开发系统世界
硬件驱动程序是界于硬件和Linux内核之间的软件接口,是一种低级的、专用于某一硬件的软件组件。这种软件组件可以使硬件与更普遍的高级应用程序接口产生互动。为某一具体的子系统或硬件端口(例如SCSI、USB或 PCMCIA)提供支持,不同于为所有SCSI、USB或PCMCIA硬件设备提供支持。由于新的硬件每天都在产生,因此测试每一个可能用于某一具体子系统的硬件是不可能的。内核只为具体的一些子系统提供支持,硬件驱动程序也只是为使用这些子系统具体的某些硬件提供支持。新内核保持高级应用程序接口与低级硬件功能的分离。用户可以通过编写合适的硬件驱动程序或修改内核,更容易地为现有系统增加对新硬件的支持。
Linux硬件驱动可以通过两种方式集成到内核中:一是将其直接编译进行内核从而一劳永逸;二是将其编写成一种目标格式,在需要添加某种硬件时,内核可以将其调入。当用户对Linux内核进行设置时,每个内核设置编译器都可显示各个可用内核设置变量的描述信息,从而使用户决定哪个变量要被消除,哪个需要写入内核,还有哪个可以编写成一种可加载内核模块。
直接将硬件驱动程序写入内核优点在于,用户可以随时对它进行调用而无需安装。但是这样大大增加内核占用的空间。将硬件驱动程序编写成一种可加载的内核模块,虽然会因为寻找驱动模块而增加系统资源的占用和运行时间,但是与庞大的内核所消耗的资源相比显得微不足道。将硬件驱动程序编写成一种可加载的内核模块,还可为软件开发提供许多便利。当用户需要对某一硬件驱动程序进行开发或纠错时,用户可以动态地卸载旧的版本并加载新的版本,但是如果用户的驱动程序已写入内核,那么必须对内核进行重新编写,并且每次对修改后的程序进行测试时,都必须重新启动系统。另外,将硬件驱动程序视为可加载的内核模块进行开发和配置,这样用户就可以将硬件驱动程序作为一种独立的系统进行升级,而不必对内核进行改动了。
用户要做的只是编译并安装可加载内核模块,其它的工作由模块自已来完成。当系统首次访问某一硬件设备时,只要存在使用 “depmod”命令建立的模块从属关系树,与之对应的模块就可以自动加载。可加载内核模块通常情况下安装在系统/lib/modules目录的一个子目录下。该子目录的名称由建立内核的Makefile中的VERSION、PATCHLEVEL、SUBLEVEL和EXTRAVERSION等变量的值决定。
Linux 2.6内核为硬件驱动程序带来一个新的、统一的框架。用户对原本运行于旧版本内核下的硬件驱动程序进行定制。新驱动程序框架通过定义各种接口,为硬件的即插即用和电源管理提供全面支持。子系统可以通过这些接口与各个驱动程序进行通信。新驱动程序框架更加明确了总线和驱动程序之间的责任界限。Linux 2.6内核还引入了sysfs文件系统为每个系统的硬件树进行分级处理。Linux 2.6内核还对可加载内核模块规定了新的命名方法,使用的是.ko扩展名,而不是旧版本标准的.o (object)扩展名。
这里将重点阐述2.6内核下的硬件驱动程序与以往内核下的硬件驱动程序在主体结构上的不同之处。
升级硬件驱动程序的基本结构
Linux 2.4内核下的硬件驱动标准模板如下:
#define MODULE
#include linux/module.h>
#include linux/config.h>
#include linux/init.h>
static int __init name_of_initialization_routine(void) {
/*
* code here
*/
}
static void __exit name_of_cleanup_routine(void) {
/*
* code here
*/
}
module_init(name_of_initialization_routine);
module_exit(name_of_cleanup_routine);
旧版本内核下的硬件驱动程序有一个普遍的问题,就是对初始化模块和清除功能的名称进行假设。当开发人员编写旧版本内核下的硬件驱动程序时,如果使用缺省的名称init_module()和cleanup_module(),那么就不需要对初始化模块和清除功能的名称进行记录。这种方法经常会出现错误,已逐渐被淘汰。在2.6内核下,用户必须使用module_init()宏和module_exit()宏对初始化和退出规程的名称进行记录。
另外,在2.6内核下,用户无论是在源代码中还是在Makefile文件中都不再需要对#define MODULE进行描述。内核搭建系统会自动对此类符号进行定义并校验。当用户为2.6内核编写硬件驱动程序时,必然会用到此类搭建系统。
要想对已有的模块进行编译,并使之加载到2.6内核,必须首先完成一些基本的结构变化。然而,当用户利用此类结构加载模块时,会注意到在标准输出设备和系统日志上会显示一个坏模块的出错信息。为了消除这条信息,用户需要为MODULE_LICENSE()宏增加一个示例,例如 MODULE_LICENSE("GPL")。这种2.4内核以后的版本才引入的宏,可以将模块定义为获得GPL Version 2或更新版本许可的模块。其它有效的值还有"GPL v2"、"GPL and additional rights"、"Dual BSD/GPL"(选择BSD或GPL许可)、"Dual MPL/GPL"(选择Mozilla 或GPL许可)和"Proprietary"。
2.6内核下硬件驱动程序最简单的类属模板如下:
#include img src="/files/misc/lt.gif">linux/module.h>
#include img src="/files/misc/lt.gif">linux/config.h>
#include img src="/files/misc/lt.gif">linux/init.h>
MODULE_LICENSE("GPL");
static int __init name_of_initialization_routine(void) {
/* code goes here */
return 0;
}
static void __exit name_of_cleanup_routine(void) {
/* code goes here */
}
module_init(name_of_initialization_routine);
module_exit(name_of_cleanup_routine);
除了硬件驱动自身所需要的变化外,在Linux 2.6内核下,与之相应的最重要的变化是在内核搭建过程中完成的。
模块搭建过程中的变化
对于所有开发可加载硬件驱动程序的人来说,对他们影响较大的一个基本变化不是源于内核源代码,而是将外部模块编译过程整合为标准的内核搭建机制。如果用户使用的不是集成开发环境(例如TimeSys公司的TimeStorm,它可以检测内核版本并自动建立Makefile),那么用户需要手工为硬件驱动程序建立Makefile。
在2.4和更旧版本的内核下,模块的开发和编译位置不受限制,只要将适当的编译标记移到命令行或模块的Makefile中就可以了。这些标记包括两个编译模块时必须的符号定义和一个指针。该指针指向包含有内核所含文件的目录。以下面的语句为例,用户可以建立一个名为 testmod.o的可加载内核模块:
#gcc -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4.21/include -O2 -c testmod.c
为2.6内核搭建模块的过程比较简单,但是要想满足所有成功编译所需要的条件就不那么容易。用户既不需要手工指定以模块为导向的说明(例如MODULE, __KERNEL__等),也不必指定新的符号(如KBUILD_BASENAME和KBUILD_MODNAME等),只要对外部模块植入标准内核搭建系统的过程进行整合就可以了。用户也不必指定诸如-O2之类的选项,因为用户编译的模块与其它可加载内核模块一样,进程会自动调用所有的强制性标志。至于 Makefile的编写就简单得多了,例如为testmod.ko模块编写的可与2.6内核兼容的Makefile如下所示:
obj-m := testmod.o。
然而,为了建立外部模块,用户必须先完成内核源代码树接口的编写。这样可以建立一些临时目录以供编译时使用。下面是一个为2.6内核构建模块的命令行。它可以从包含模块源代码目录下执行:
# make -C /usr/src/linux-2.6.1 SUBDIRS=$PWD modules
此示例命令假设用户的模块源代码和Makefile所在的目录与用户正在运行的命令中的相同。如果用户不使用POSIX命令(例如BASH),那么可以通过“SUBDIRS=`pwd`”命令,用$PWD变量代替SUBDIRS参数。这样用户就可以使用“pwd”命令识别工作目录。建立出口的命令如下所示:
#make: Entering directory `/usr/src/linux-2.6.1‘
*** Warning: Overriding SUBDIRS on the command line can cause
*** inconsistencies
make[1]: `arch/i386/kernel/asm-offsets.s‘ is up to date.
Building modules, stage 2.
MODPOST
CC /home/wvh/timesys/2.6/testmod/testmod.mod.o
LD [M] /home/wvh/timesys/2.6/testmod/testmod.ko
#make: Leaving directory `/usr/src/linux-2.6.1‘
"make"命令的成功完成将产生testmod.ko模块。对该模块的命名使用的是新的内核模块命名规则。如果用户已经对系统的启动程序进行了修改,以便通过名称清楚地加载模块,那么用户需要确定在升级到2.6内核后,这些模块的命名是否遵循了新的命名规则。
适应2.6内核的内部变化
Linux 2.6内核还带来了许多内部变化,用户需要改变已有的驱动程序以适应这种变化。这些变化包括内核的异步I/O机理、DMA支持层、存储器与页分配机理、数据块硬件驱动程序和新的类属硬盘接口等。例如,用来分配并管理存储器与页的功能就发生了新的变化。在2.6内核下,系统使用了一种名叫mempool的标准接口。对模块参考计数的使用和管理也发生了变化。模块参考计数主要用于决定一个模块是否正在使用,并对没有被使用的模块进行安全卸载。在2.6内核下,命令序列已被工作序列所代替,其中,对大量不同驱动程序产生影响的一个重要变化是参数模块的新接口。MODULE_PARM()宏已由详细的参数说明所代替。这种说明来源于新的module_param()宏。
Linux 2.6内核的优先能力和对SMP的识别能力,为驱动程序编写人员带来一些新问题。在单处理机系统中,在无优先能力的Linux内核下,一些驱动程序可以假设在两个处理器间不必再提供重入接口,因为它们无法同时运行驱动程序。驱动程序可以使用“spinlock”或“mutex”命令来保护那些可从多进程访问的数据。这些问题的考虑对于为嵌入式环境(如TimeSys Linux)编写高性能和实时硬件驱动程序的人来说尤为重要。
其它考虑因素
如果用户较为依赖Linux 2.6内核的工作,那么还需要对驱动程序做一些其它的改动。例如,尽管自从2.3内核诞生后,devfs文件系统已经被写入内核,并且在2.6内核设置中被标注为舍弃指令,但是它却经常在一些特殊领域中使用。例如在嵌入式计算中, devfs可提供较强的灵活性和一个紧缩的/dev文件名。devfs文件系统是介于hardcoded硬件节点间的中间步骤。此类节点主要用于早期的 Linux和Unix系统中。同时,它还是udev、hotplug和sysfs文件系统的综合。对udev的支持技术目前正在被写入Linux 2.6内核。TimeSys公司已经开发出了拥有此类技术的商业Linux系统。如果用户正在使用其它的Linux发行版,那么用户也许会发现devfs 支持和集成技术对于驱动程序来说十分重要。
如果用户想使用devfs文件系统,那么必须首先在搭建内核时激活对它的支持。这一步可以在内核设置编辑程序的File systems→Pseudo filesystems中完成。使用devfs还需要改变硬件驱动程序对硬件节点的识别方法。当用户使用传统的/dev目录作为Linux硬件描述符文件的放置位置时,硬件驱动程序通过启动register_blkdev() 或register_chrdev()函数来注册新硬件。具体使用哪一个,要看驱动程序注册的是一个数据块硬件还是字符硬件,而且必须事先知道硬件的主号码和次号码。另外,因为udev是一个可热插拔程序,它可以自动建立并删除/dev目录下的登录项,所以这一方法同样也适用于新的udev硬件机理。
使用devfs硬件文件系统时,硬件驱动程序必须使用devfs_register()系统呼叫来注册它们的硬件。驱动程序可以继续使用此前指定的主次编码,也可通过为devfs_register()呼叫指定DEVFS_FL_AUTO_DEVNUM 标志,由devfs自动指定编码。
小结
用户常常由于提高系统性能、增加系统功能、实现系统单一化和标准化等原因对内核进行修改。每一个新版本Linux内核都会带来许多新的变化,这些变化在不同层次上对开发人员有很大的影响。本文概括了在2.6内核下硬件驱动程序的变化及模块搭建过程的变化。诸如TimeStorm之类的工具可以为用户提供升级驱动程序的模板,并可自动为可加载内核模块建立并管理Makefile。然而,如果用户正在手工维护现有的硬件驱动程序或开发新的硬件驱动程序,那么用户将需要认真的考虑2.6内核的变化,做出正确的选择。
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。