静下来,定好方向,好好干。
分类: WINDOWS
2008-04-24 14:01:40
操作系统 | 应用程序访问接口方式 | 访问权限 |
DOS | 直接访问 | 所有[注] |
Windows95/98 | 通过设备驱动程序*.VXD | 所有[注] |
直接访问 | 仅I/O端口 | |
WindowsNT | 通过设备驱动程序*.SYS | 所有[注] |
[注]‘所有’指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管理器根据需要调用这些函数,对于一个需要处理中断的最简单的驱动程序也需要由以下几个函数构成:
1.DriverEntry() 运行于PASSIVE_LEVEL
驱动程序入口点,当驱动程序被手动或自动装入系统后,驱动程序从这点开始执行,主要用于定位硬件资源,建立指向其它驱动程序函数的指针等其它初始化工作。
2.XxUnload() 运行于PASSIVE_LEVEL
用于驱动程序从系统卸出之前,释放由驱动程序占用的所有系统资源。
3.XxIsr() 运行于DIRQL
中断服务程序。
4.XxDpcForIsr() 运行于DISPATCH_LEVEL
中断服务程序后处理程序,以排队方执行不太关键代码的执行,由于排队机制及优先级,不会造成代码拥塞从而提高中断服务程序的响应并且提高系统总体I/O吞吐率。
5.XxOpen() 运行于PASSIVE_LEVEL
处理应用程序Win32函数CreateFile()请求。
6.XxClose() 运行于PASSIVE_LEVEL
处理应用程序Win32函数CloseHandle()请求。
7.XxDispatch() 运行于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
来源 | IRQL |
硬件 | HIGHEST_LEVEL |
POWER_LEVEL | |
IPI_LEVEL | |
CLOCK2_LEVEL | |
CLOCK1_LEVEL | |
PROFILE_LEVEL | |
DIRQLs(I/O设备中断平台相关的级数) | |
软件 | DISPATCH_LEVEL |
APC_LEVEL | |
PASSIVE_LEVEL |
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:应用程序查询驱动程序内部的中断累积计数器的值。
第四步,画流程图。这里列举本例实现的几个主要流程图,图中略去所有错误检查部分。
图3-1为DriverEntry()流程图。几点说明:
系统传给驱动程序入口函数系统定义的‘设备驱动对象’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请求包)的AssociatedIrp.SystemBuffer域中,然后把输入数据拷贝到这个池中,在第一段程序中cmd的值TRUE被拷贝到池中,这样驱动程序通过RtlCopyBytes()函数再把池中的值拷贝到驱动程序的RunFlag中。IOCardADispatch()函数执行完,I/O管理器把池中的内容拷贝到调用者的输出缓冲区,在第二段程序中,驱动程序通过RtlCopyBytes()函数把counter的值拷贝到池中,从而最终传递到应用程序变量w中。
第五步,编程。在编写设备驱动程序的同时,要编写一个简单的应用程序用于测试设备驱动程序的一些功能。
第六步,驱动程序的载入。
驱动程序C语言源程序经过编译、连接生成扩展名为SYS的文件,本例为IOCardA.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提供windbj.exe程序用于设备驱动程序的调试,调试设备驱动程序需要两台CPU体系结构完全相同的计算机,一台为‘宿主机’,运行windbj.exe程序,另一台为‘目标机’,运行设备驱动程序,两台计算机用串口线连好,进行一系列软件设置(参见[1]第17章),就可以开始调试了,从‘宿主机’可以控制及观察‘目标机’上驱动程序的运行情况。最常用的调试手段是在驱动程序中必要的位置加入DbgPrint()函数,这个函数可以把指定信息输出到‘宿主机’windbg.exe窗口中,通过分析这些信息,可以了解驱动程序当前的运行情况。