引言:
由于工作关系,我经常涉及pc机与外围设备接口的工作,从pc机这方面要做的工作看来,主要是通过接口处理外围设备的中断,通过i/o端口或内存地址与外设互相传递数据。从计算机原理的角度看,所要达到的目的很简单,那么如何编写程序完成上述功能呢?
目前国内流行的pc操作系统有三种:dos,win95/98系列,windowsnt。dos是单用户、单任务操作系统,由于pc机硬件处理速度不断提高,基于单用户、单任务的操作系统越来越不能充分发挥硬件的功能,现在只应用于一些老式pc及其它个别场合,有逐渐被淘汰的趋势;win95/98系列和windowsnt属于多任务操作系统,不论从其原理还是界面上看,这两种操作系统都比dos有着无可比拟的优越性,这两种操作系统虽然在界面和操作上及其相似,但其内部实现的诸多方面有许多区别,有些区别是本质上的。win95/98设计目标是针对一般家庭用户,安全性及可靠性存在许多薄弱环节,就可靠性而言,win95/98系列不能很好的防止多任务环境中某个进程的非法操作导致系统中其它程序甚至整个系统的崩溃,而windowsnt在这方面及其它诸多方面设计的相当严谨。这两种操作系统是microsoft公司同一时期的产品,但针对不同的使用群,所以在一些重要场合及生产实践中应该选择windowsnt作为计算机的操作系统,此外,从发展趋势来看,windowsnt已经成为定型产品,具有相对稳定性。
在不同操作系统下编写驱动程序是有很大区别的,在dos平台上,应用程序和设备驱动程序之间没有标准的接口,它们在外部表现为一个扩展名为exe的文件,驱动程序的作用被柔和在应用程序中,这样,应用程序为了使用不同厂商的同一类设备,必须了解这些设备在接口上具体的硬件实现,同时,对于一个特定型号的硬件产品,所有支持它的应用软件中对于控制整个设备动作的这部分代码,可能被多次重写。这种情况不适应硬件及应用软件的飞速发展。windows系统在这方面,进行了根本性改进,把控制设备动作的这部分代码独立出来,提出了设备驱动程序的概念,驱动程序是应用程序和硬件设备之间的一个桥梁,应用程序与驱动程序之间有明确的接口,应用程序通过与驱动程序交换信息,达到控制外设的目的。接口定义的操作是面向设备的,这就是说,在应用程序的设计中,并不用关心对外设操作的具体硬件实现,只是对驱动程序发出一系列指令既可;驱动程序接受来自上层应用程序的指示,具体操纵实际硬件,完成用户功能。具体实现上,win95/98系列与windowsnt又有所区别,windowsnt是严格按照上述思路设计的;而win95/98系列不那么严格,其支持上述思路,但同时应用程序也可以绕过驱动程序直接访问实际物理i/o,这样做,增加程序设计的灵活性,但同时,对系统可靠性造成一定隐患。这也正是win95/98系列可靠性低于winnt的原因之一。
表1-1 三种操作系统下访问接口比较
[注]‘所有’指i/o端口,ram总线,中断,dma。
windowsnt设备驱动程序的组成原理
windowsnt操作系统结构分为用户模式和内核模式,用户模式下的编程为应用程序的设计,而开发设备驱动程序,则属于内核模式下的编程,内核模式组件包括nt executive(exxxx),内核(kexxx),硬件抽象层(halxxx)。其层次如图2-1所示,其中nt executive 包括几个独立的软件组件,它们是系统服务接口(zwxxx),对象管理器(obxxx),配置管理器,进程管理器(psxxx),安全监视器(sexxx),虚拟空间管理器(memxxx),本地进程调用,i/o管理器(ioxxx)。内核模式的系统服务并不是全部公开的,而是提供了一系列开发设备驱动程序需要的函数(上文括号内为函数形式,函数手册参见[2]kernel-mode drivers-reference章节),换言之,这些函数功能是所有内核模式的系统服务功能的子集。
驱动程序由一系列相对独立的函数组成,由i/o管理器根据需要调用这些函数,对于一个需要处理中断的最简单的驱动程序也需要由以下几个函数构成:
entry() 运行于passive_level
驱动程序入口点,当驱动程序被手动或自动装入系统后,驱动程序从这点开始执行,主要用于定位硬件资源,建立指向其它驱动程序函数的指针等其它初始化工作。
ad() 运行于passive_level
用于驱动程序从系统卸出之前,释放由驱动程序占用的所有系统资源。
() 运行于dirql
中断服务程序。
orisr() 运行于dispatch_level
中断服务程序后处理程序,以排队方执行不太关键代码的执行,由于排队机制及优先级,不会造成代码拥塞从而提高中断服务程序的响应并且提高系统总体i/o吞吐率。
() 运行于passive_level
处理应用程序win32函数createfile()请求。
e() 运行于passive_level
处理应用程序win32函数closehandle()请求。
atch() 运行于passive_level
处理应用程序win32函数deviceiocontrol()请求,通过一系列自定义命令,驱动程序与应用程序交换特定的信息。
windowsnt使用一个抽象化的cpu优先级方案, irql代表中断请求级,任一时刻cpu总处在某一级上,这个数越大,表示当前的任务重要性越大,如表2-1所示,从上至下irql越来越小。所有上述驱动程序的函数及内核模式函数都必须运行于各自的irql级上,如果违反这一调用规定,会造成系统崩溃。例如,中断服务程序(xxisr)运行于dirql及上,那幺在编写中断服务程序时,只能调用允许在这一级运行的内核模式函数(并不是所有内核模式函数都能运行于dirql级)。至于每个内核模式函数运行级别的说明,详见[2]kernel-mode drivers-reference章节。
windowsnt是一多任务系统,许多设备的驱动程序同时存在系统中,这样各个设备所占用的资源(中断,i/o及ram地址空间)很有可能冲突,如果设备驱动程序在运行之前不进行‘探测’而使用自己硬件设备的资源,有可能和系统内其它设备占用的资源冲突,后果不堪设想。windowsnt通过注册表管理硬件资源的占用信息,作为内核模式信任的组件,驱动程序使用硬件资源之前必须遵循‘查询-申请-使用-释放’的原则(如图2-2所示)。
表2-1
windowsnt设备驱动程序的编写步骤与实例
现以一实际例子简要说明设备驱动程序的开发步骤,本例以cinrad天气雷达测试卡实际应用为原型,加以简化、抽象。
第一步,了解被控设备的接口情况。
本例为一isa卡,占用pc机9号中断,i/o地址360h及ram地址d0228h分别一个字空间。
第二步,确定驱动程序的功能。
驱动程序每当9号中断达到时,检查运行标志变量runflag(为一bool变量),如果等于true,中断累积计数器counter(为一unsigned short变量)增一,把这个值写入ram地址d0228h,再从这个地址读出,如果读出值等于写入值,把这个值写入i/o地址360h,这个地址的内容会驱动板卡上的led显示,把写入值显示出来;如果读出值不等于写入值,设置运行标志变量false。如果运行标志变量等于false,什幺也不做,返回。
第三步,定义驱动程序与应用程序的软件接口。
本例定义两个接口命令:
ioctl_iocarda_start:应用程序设置驱动程序内部的运行标志变量等于true。
ioctl_iocarda_read:应用程序查询驱动程序内部的中断累积计数器的值。
第四步,画流程图。这里列举本例实现的几个主要流程图,(图略)。
系统传给驱动程序入口函数系统定义的‘设备驱动对象’drobj,通过初始化这个对象的一些成员变量,把驱动程序其它函数与这个对象联系起来。
isa卡为非即插即用设备,事先把资源占用信息手工添加注册表如下:
[hkey_local_machine\system\currentcontrolset\services\iocarda\parameters]
"irq"=dword:00000009
"iospan"=dword:00000004
"ioadd"=dword:00000360
"ramadd"=dword:000d0228
"ramspan"=dword:00000002
其中iocarda以下各子键及其值为自定义,设备驱动程序利用相应函数检索出这些值。
(3)每个设备驱动程序可以创建若干系统定义的‘设备对象’,本例根据需要只创建了一个‘设备对象’dev。‘设备对象’其中一个成员变量为指向一非分页的物理内存块deviceextension,这块内存大小及内容为用户自定义,由于dev或deviceextension对象会被系统传给驱动程序的其它函数,这样驱动程序各函数通过访问这块内存区,实际上达到互相传递信息的功能。本例在这里存储设备硬件资源信息及runflag和中断计数器counter,这些数值在driverentry()初始化后,供驱动程序的其它函数使用。
图3-2为中断服务程序iocardaisr()流程图。操作系统接受中断,连同deviceextension等参数传给中断服务程序,中断服务程序利用这些参数,实现要求功能。
图3-3为iocardadispatch()流程图,这个函数用于处理来自上层应用程序的命令。上层应用程序通过以下程序段设置驱动程序中runflag值为true,从而启动中断服务程序开始计数。
bool cmd=true;
htest = createfile(...); //打开设备
deviceiocontrol(htest, //设备句柄
ioctl_iocarda_start,//命令
&cmd,sizeof(bool), //输入缓冲区地址及大小
null,0,&c,null);
closehandle(htest); //关闭设备
上层应用程序通过以下程序段查询当前的中断计数器的值并存于变量w中。
unsigned short w;
htest = createfile(...);
deviceiocontrol(htest,
ioctl_iocarda_read, //命令
null,0,
&w,sizeof(unsigned short),//输出缓冲区地址及大小
&c,null);
closehandle(htest);
其中deviceiocontrol()执行后,操作系统调用iocardadispatch()函数,如流程图所示,这个函数内部通过一个开关语句,根据命令执行相应的分支。驱动程序与应用程序通过此函数接口交换数据时,操作系统提供4种可选数据缓冲方式,本例由于数据i/o量比较小,故选用‘缓冲i/o’ (method_buffered)。过程是,i/o管理器首先分配一个非分页池,它的大小为调用者输入缓冲区和输出缓冲区的较大者,第一段程序为sizeof(bool),第二段程序为sizeof(unsigned short),它的地址存到irp(i/o请求包)的buffer域中,然后把输入数据拷贝到这个池中,在第一段程序中cmd的值true被拷贝到池中,这样驱动程序通过rtlcopybytes()函数再把池中的值拷贝到驱动程序的runflag中。iocardadispatch()函数执行完,i/o管理器把池中的内容拷贝到调用者的输出缓冲区,在第二段程序中,驱动程序通过rtlcopybytes()函数把counter的值拷贝到池中,从而最终传递到应用程序变量w中。
第五步,编程。在编写设备驱动程序的同时,要编写一个简单的应用程序用于测试设备驱动程序的一些功能。
第六步,驱动程序的载入。
驱动程序c语言源程序经过编译、连接生成扩展名为sys的文件,本例为,把这个文件拷贝到\winnt\system32\drivers\系统目录下,同时手工添加如下信息到注册表:
[hkey_local_machine\system\currentcontrolset\services\iocarda]
"errorcontrol"=dword:00000001
"start"=dword:00000003
"type"=dword:00000001
要保证iocarda子键名与驱动程序文件名一致,其中type=1表示此驱动程序为内核模式驱动程序,start=3表示此驱动程序手动载入,errorcontrol=1表示当驱动程序发生错误时,日志记录错误并显示一个消息框。这样当windows重新启动后,通过使用控制面板中的device小应用程序,从列表中找到iocarda设备名,按start按钮,于是,设备驱动程序就驻留内存并在底层开始工作了。
第七,调试。设备驱动程序没有界面,完全在系统底层运行,为了观察驱动程序的运行状态,windowsnt ddk提供程序用于设备驱动程序的调试,调试设备驱动程序需要两台cpu体系结构完全相同的计算机,一台为‘宿主机’,运行程序,另一台为‘目标机’,运行设备驱动程序,两台计算机用串口线连好,进行一系列软件设置(参见[1]第17章),就可以开始调试了,从‘宿主机’可以控制及观察‘目标机’上驱动程序的运行情况。最常用的调试手段是在驱动程序中必要的位置加入dbgprint()函数,这个函数可以把指定信息输出到‘宿主机’窗口中,通过分析这些信息,可以了解驱动程序当前的运行情况。
结束语
windowsnt是一个复杂而严密的系统,驱动程序的开发不可避免的涉及现代操作系统理论及其它许多计算机理论,内涵相当广泛,本文围绕着开发实践从一定深度探讨了windowsnt设备驱动程序开发最基本的知识及一般方法,希望对读者有所帮助,对于复杂,特殊应用的实现及编程技巧,有待于读者在各自实际应用中不断探索。
参考文献
1.《windowsnt设备驱动程序设计指南》 art baker著 机械工业出版社
oft co. windowsnt 4.0 device driver kid
中国论文网(www.lunwen.net.cn)免费学术期刊论文发表,目录,论文查重入口,本科毕业论文怎么写,职称论文范文,论文摘要,论文文献资料,毕业论文格式,论文检测降重服务。