关键词: LKM 核心驱动 温度采集卡 工业控制 ISA
Linux操作系统经过十多年的发展,以其运行稳定可靠、占用资源少、长期运行仍能保证效率的特性,获得了众多的企业和政府部门的认可。近几年,在工业控制中Linux也有非常好的表现,控制领域中RTLinux应用研究正在进行。笔者开发了Linux供热控制平台。在此Linux控制系统应用研究中着重研究了核心可加载驱动模块机制。本文将详细讨论采集卡的核心驱动模块。
1 硬件介绍
P51系列A/D采集卡是一个8通道ISA总线的温度采集卡。它将温度传感器(热电偶、热电阻等)的输出信号或电流、电压通过A/D转换器转换成数字量。其主要特点:精度高、抗干扰能力强、可靠性高。
1.1 P51采集卡的工作过程
图1说明了接口板采集数据的整个过程。
每个通道数据以16位二进制原码形式输出,低字节在前。8个通道16字节数据按顺序输出。
1.2 I/O端口
主机与接口板的接口使用2个输入端口,由于A0未参加译码,实际上占用了4个端口地址。接口板的地址可由板上的跳线开关设定。地址默认设置为164H及166H,如表1所示。
该硬件只用到了2个I/O端口:状态口和数据口,字长都是8位。其中,状态口只用D0位作为检验数据是否已经准备好的标志,其它位没有定义。
2 软件部分
软件部分由应用程序和设备驱动程序两部分构成,本文主要讨论设备驱动程序部分。在Linux平台上实现对硬件的驱动支持可以有两种方式:一种直接在用户空间实现;另一种使用Linux内核中提供的机制来实现。考虑到用户空间驱动程序的局限性,在开发中采用了第二种方式。
2.1 LKM机制简介
Linux内核提供了两种机制来开发设备驱动程序:一种直接把驱动程序联编到内核中;另一种则是通过称为Linux可加载模块(LKM)机制来开发可动态加载和卸载的驱动模块。基于各方面的考虑本文采用后者。
Linux作为单核结构效率比较高,但是系统灵活性不足。为了平衡这两者的关系,提供了LKM 机制。利用这种机制可以开发Linux内核模块,并且可以动态地对它加载和卸载。Linux下的设备驱动程序一般都支持这种方式,且模块被加载到内核后,它就可以任意利用内核提供的各种资源和服务了。Linux内核维护了一张所有内核资源的符号表(称为内核资源符号表),用于在模块载入时解决相应资源的引用问题。并且,Linux允许模块的堆栈操作,一个模块可以使用其他模块提供的资源。也就是说:一个模块对另一个模块资源的使用与其对内核资源的使用非常相似,不同的只是这些服务的资源从属于另一个模块而已。每当一个模块被加载,Linux就会修改内核资源符号表,将该模块所提供的服务和资源加入进去。这样另一个模块载入时,如果需要就可以引用这个模块的资源了。
卸载一个模块时,需要知道当前模块是否正在被使用。如果没有被使用,在卸载时要能够通知该模块它将被卸载,以便由它自己释放已被它占用的系统资源。同时Linux还要从内核资源符号表中删除该模块提供的所有资源和服务。
从上面的原理分析可知,内核模块编写时,有两个主要的接口函数:init_module()用于在模块加载时注册服务和申请资源;cleanup_module()用于在模块卸载时清除掉由init_module()所做的工作,从而使内核模块可以安全地卸载。其中对init_module()的调用是在根用户执行insmod命令加载模块时。而对cleanup_module()的调用是在根用户执行rmmod命令卸载模块时。
2.2 Linux下设备驱动程序
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件细节。在应用程序看来,硬件设备只是一个设备文件, 可以通过相应的系统调用象操作普通文件一样对硬件设备进行操作。
2.2.1 Linux设备分类
Linux支持两种标准硬件设备:块设备和字符设备。块设备接口仅支持面向块的I/O操作,所有I/O操作都通过在内核地址空间中的I/O缓冲区进行,它可以支持几乎任意长度和任意位置上的I/O请求,即提供随机存取的功能。字符设备接口支持面向字符的I/O操作,只支持顺序存取的功能,一般不能进行任意长度的I/O请求。I/O请求的长度必须是设备要求的基本块长的倍数。
2.2.2 设备标识方式
Linux设备由一个主设备号和一个次设备号标识。主设备号唯一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中相应表项的索引。次设备号仅由设备驱动程序解释,一般用于识别在若干可能的硬件设备中,I/O请求所涉及到的那个设备。值得一提的是次设备号还可以被分成几个部分,用来区分子设备驱动程序和具体的设备。
2.2.3 Linux设备驱动程序组成部分
Linux设备驱动程序可以分为三个主要组成部分:
(1)自动配置和初始化子程序。负责检测所要驱动的硬件设备是否存在并能否正常工作。如果该设备正常,则对这个设备及其相关的设备驱动程序需要的软件状态进行初始化。
(2)服务于I/O请求的子程序。主要是file_operations结构的各个入口点的实现。这部分的实现支持文件系统调用(如open、close、read等)。
(3)中断服务子程序。在Linux系统中,并不是直接从中断向量表中调用设备驱动程序的中断服务子程序,而是由Linux系统接收硬件中断,再由系统调用中断服务子程序。
2.3 温度采集卡驱动程序内核模块
2.3.1 file_operations结构的初始化
file_operations结构是Linux操作系统中用于实现驱动程序的最重要的数据结构,它为Linux提供的服务于I/O请求的子程序的代码实现提供了一系列入口点。该结构贯穿在整个驱动程序中,笔者在文件作用域内进行了定义,并对本程序中用到的入口点做了初始化,其伪代码如下:
struct file_operations p51_fops=}
open : p51_open; //把实现的p51_open函数指针赋给open入口点。
release : p51_release; //把实现的p51_release函数指针赋给release入口点。
read : p51_read; //把实现的p51_read函数指针赋给read入口点。
{
2.3.2 模块初始化与模块卸载
温度采集卡模块初始化通过对init_module()的实现来完成以下几个任务:
(1)以字符设备类型向系统注册温度采集卡设备,同时动态获得主设备号。通过调用下面这个函数来实现:
int register_chrdev(unsigned int major, const char * name, struct file_operations *fops);
这里使major参数为0,这样系统就会动态地分配并返回主设备号。name参数是用于标识设备的字符串。file_operatons传入的是如前所述的p51_fops。
(2)向系统申请温度采集卡的I/O端口地址。根据前面提到的跳线方法得到I/O地址,调用系统提供的宏:
check_region(start,n) //检查端口地址范围start~start+n-1是否可用,是则返回0,否则返回1。
request_region(start,n,name) //用于申请通过上述函数检查的地址范围。
(3)做一些必要的系统日志,根据各种条件用printk向系统日志缓冲区写入不同级别的信息。
(4)控制内核资源提供的符号表输出的符号信息(即在LKM简介部分提到的模块要注册的服务)。这里使用EXPORT_NO_SYMBOLS使得该模块不输出任何符号信息。
温度采集卡模块卸载需要完成以下几个任务:
(1)调用release_region(start,n)宏,释放模块初始化时申请的I/O端口资源。
(2)调用int unregister_chrdev(unsigned int major, const char * name)向系统注销该字符设备。本程序中major参数是前面注册时动态获得的主设备号,name与注册时提供的name字符串相同。
(3)调用printk函数,做一些必要的系统日志。
2.3.3 file_operations结构中入口点的实现
(1)open和release入口点
这两个入口点在本模块中被赋予的是file_operations结构的p51_open和p51_close函数指针。它们主要通过调用MOD_INC_USE_COUNT及MOD_DEC_USE_COUNT来进行模块计数。用计数来控制温度卡驱动模块是否正在被使用,防止模块使用中被意外卸载,导致核心对设备操作出现异常。
(2)read入口点的实现
这个入口点在本模块中被赋予的是file_operations结构的p51_read函数指针,它是设备操作的核心部分,实现了如下几个功能:
·用inb_p宏访问硬件的状态和数据端口,以读取相应的状态和数据信息。
·调用long sleep_on_timeout(wait_queue_head_t *q, long timeout)把当前进程加入时钟等待队列q中,等待timeout时间。从采集卡的工作方式来看,这样做可以减少轮询时间,大大提高了效率。
·如前所述,程序从温度卡读取的是数据的原码形式,须实现原码与补码之间的转换。
·Linux分为核心空间和用户空间。用户空间的代码不能直接访问核心空间,故需调用Linux核心提供的copy_to_user(to,from,n)宏,把数据从内核空间地址from拷贝到用户空间地址to中。这样,系统调用返回后,用户空间的代码就可以通过to指针来访问相应的数据并进行处理了。
数据采集核心部分如图2所示。
2.3.4 温度卡故障的处理
故障处理在工业控制中非常重要。由于本采集卡在硬件一级对故障预报和发现提供的支持比较少,故在软件层次上,在满足需求的情况下通过对状态口依照前面所提供的方法查询三次失败后,将重新对设备进行初始化和设置,再作以上尝试。如果还是失败将由用户进程进行处理。
2.4 应用程序开发
对上述模块编译并加载后,Linux根用户可用mknod命令,利用动态分配的主设备号(该设备号在用户空间,可以从/proc/devices文件中获得)建立相应的设备文件,并对它设置恰当读写权限。就可以在应用程序中,使用Linux的文件系统调用这个设备文件来操作温度采集卡。这样做使应用程序编程风格更加统一,代码更具鲁棒性,应用系统更加安全,更易于维护。而且可在核心级保证关键部分的实时响应,从而降低了用户程序开发的难度。
对于Linux下的工业控制软件开发,还有很长的路要走。而其中非常重要的基础性工作就是对各种工控设备提供稳定可靠的核心驱动模块的支持。本文所作的工作对此进行了一次很好的尝试。