设计Linux设备驱动程序设计的教程
设计Linux设备驱动程序设计的教程
摘要:本文介绍了Linux设备驱动程序设计的相关基本知识,总结了2.4与2.6不同版本内核下Linux设备驱动程序的设计模版与编写方法,并给出了一个实例化的设备驱动程序样本。
关键词:静态链接模块,可加载模块,字符设备,块设备,初始化,设备接口,中断请求,中断服务,设备号
一.Linux设备驱动程序设计基础
Linux设备驱动程序是内核与硬件间的一层软件接口,实现了高级应用程序与硬件互动。它可以通过两种方式集成入内核中:一是将其直接编译和静态链接到内核,从而一劳永逸;二是通过称为Linux可加载模块(LKM)机制,将其编写成一种目标格式,实现成为可动态加载和卸载的驱动模块。前者用户可随时调用它而无需安装但增加了内核占用空间,并且更新需重编内核、重起系统;后者虽然使用前须先加载,但是更节省资源且灵活,也是通常采用的设备驱动设计方式。
Linux设备驱动程序有三种类型:字符设备、块设备和网络设备。字符设备一次I/O操作存取数据量不固定,只能顺序存取,如鼠标、磁带驱动器等设备。块设备一次I/O操作以固定大小的数据块为单位,如硬盘、软驱等。其中字符设备不经过系统的快速缓冲,而块设备经过系统的快速缓冲。网络设备是特殊处理的,它没有对应的设备文件,Linux使用套接口(socket),以文件I/O方式提供了对网络数据的访问。其中与文件系统相关的两种类型是字符设备和块设备。 (本文主要讨论字符设备和块设备驱动程序的编写。)
不管是何种类型,从结构上看,整个驱动程序可分为驱动程序初始化、独立于设备的接口和硬件I/O部分三个部分。驱动程序初始化部分负责将设备驱动程序装载到内核或从内核中卸载等;独立于设备的接口是设备驱动程序和文件系统连接的桥梁;而硬件I/O部分具体实现各种I/O操作。(如图)
从程序实现角度,设备驱动程序也可分为以下三个部分:(1)自动配置和初始化子程序。负责检测所要驱动的硬件设备是否存在并能正常工作,如果该设备正常,则对这个设备及其相关的设备驱动程序需要的软件状态进行初始化。(2)服务于I/O请求的子程序。主要是file operations结构的各个入口点的实现。这部分的实现支持文件系统调用(如open,lose,read,write等)。(3)中断服务子程序。在Linux系统中,并不是直接从中断向量表中调用设备驱动程序的中断服务子程序,而是由Linux系统接收硬件中断,再由系统调用中断服务子程序。
和Unix一样,Linux把设备均作为文件来对待。这些文件一般称为特殊文件,它使用户或应用程序可按操纵普通文件的方式进行访问控制硬件设备。在Linux内中,设备驱动程序是作为文件系统的一个模块存在的。它向下负责和硬件设备的交互,向上通过一个通用的接口挂接到文件系统上,从而和系统的内核等联系起来,管理和控制各种设备,是软件和硬件设备的一个抽象层。
设备文件的属性包括:文件名、设备类型、主设备号、次设备号。主设备号是与驱动程序一一对应的。次设备号用来区分使用同一个驱动程序的个体设备。major()函数获得主设备号,minor()函数获得次设备号。与普通的目录和文件一样,对设备的操作也是通过对文件操作的file_operations结构体来调用驱动程序的设备服务子程序。作为实现驱动程序的最重要的数据结构,它为Linux提供的服务于I/O请求的子程序的代码实现提供了一系列入口点,它们在设备驱动程序初始化的时候向系统进行登记,以便系统在适当的时候调用。
在Linux中,一个设备在使用之前必须向系统进行注册,设备注册是在设备初始化时完成的。在系统内核保持着一张字符(或块)设备注册表,表的结构是数组,其下标值就是主设备号,且每种字符(或块)设备占一个表项。例如,字符设备注册表是结构数组chrdevs[],而块设备注册表是结构数组blkdevs[]。Linux的内核模块被加载到内核后,它就可以根据设备注册表,任意地利用内核提供的各种资源和服务了。
当系统首次访问某硬件设备时,只要存在使用“depmod”命令建立的模块从属关系树,与之对应的模块就可以自动加载。可加载内核模块通常情况下安装在系统/lib/modules目录的一个子目录下。
对于可加载的内核驱动程序模块,它类似Windows系统的动态连接库(dl1),并有两个主要接口函数:一个用于在模块加载时注册服务和申请资源,如init module();另一个用于在模块卸载时清除由前者所作的工作,从而使内核模块可以安全地卸载,如cleanup module()。编译后,root用户执行insmod命令加载模块时调用前一个函数,执行rmmod命令卸载模块时调用后一个函数。(不同内核版本接口函数有所不同,具体请参照下文。)
一.Linux设备驱动程序的编写
考虑Linux内核版本的差异,我们分旧的2.4内核与新2.6内核讨论:
A. 基于Linux 2.4内核的设备驱动程序编写
1.先给出Linux 2.4内核的设备驱动参考模板,并据此实现初始化(卸载)功能:
Linux设备驱动程序的初始化具体内容可用下面的伪函数进行描述:(为表达方便和实际编程时参考,采用类C语言的描述方法进行描述,其中括号[]间的内容表示可眩)
static int __init name_of_initialization_routine(void) {
/*完成硬件相关参数的设置、硬件初始化;申请内存(要用kmalloe)等资源;(注:若前面加static表示函数作用域限制在本文件之内;int,表示函数返回一个整型值,_init表示告诉gcc,生成的模块中,把该函数放在init这个section;init,是函数名,init section的代码/数据都只是在内核初始化的时候用到,所以初始化完成以后就可以释放了,内核的启动信息freeing init memory就是指的这个)*/
[用cheek_region()测试I/O端口是否可用(是否被其它设备锁定);]
[用request_region()锁定所用的端口区域;]
#if(字符设备)
用register_chrdev()向内核注册主设备号;
#endif
#if(块设备)
初始化struct blk_dev_struct结构变量bIk_dev[major],其中major为注册设备号;
用函数register_blkdev()向内核注册主设备号;
#endif
#if(采用中断方式)
用request_irq()将中断处理函数排队列以接收处理中断
#endif
#if(采用轮询方式)
用queue_task()或add_timer()等将相应函数排入合适的队列接收定时器报时
#endif
#if(需要DMA)
request_dma()
#