Windows CE的驱动程序可以从多种角度进行区分。
可以分为本机设备驱动(Built-In Driver)、可加载驱动(Loadable Driver)以及混合型驱动。
(1)本机设备驱动
本机设备驱动即Native Device Drivers。这些驱动程序在系统启动时,在GWES的进程空间内被加载,因此它们不是以独立的DLL形式存在。这些驱动对应的设备通常在系统启动时就被要求加载,如果没有串口,也没有LCD的话,整个系统就不能和用户信息交流。另外,流驱动程序也能作为本机设备驱动而存在。
(2)可加载驱动
也被称为流驱动。
这些驱动可以在系统启动时或者和启动后的任何时候由设备管理器动态加载。通常它们以DLL动态链接库的形式存在,系统加载它们后,这些驱动程序也只是以用户态的角色运行。可加载驱动程序通过文件操作API来从设备管理器和应用程序获得命令。
在Windows CE中典型的可加载驱动有以下各类:
n PCMCIA driver(PCMCIA.dll)
n Serial driver(SERIAL.dll)
n ATAFLASH driver(ATA.dll)
n Ethernet driver(NE2000.dll,SMSC100FD.dll)
(3)混合型驱动
这类驱动综合了前两种驱动的特性。它同时使用了stream接口和custom-purpose接口。
混合型驱动主要是提供custom-purpose 接口,但是由于需要和系统中只允许使用stream接口的那些模块进行交互,因此也必须提供stream接口。例如,PC card socket驱动同时拥有两套接口。
可以分为独立驱动和层次型驱动。图9-1是这两种驱动在系统中的位置。
图9-1 独立驱动和层次型驱动在系统中的位置
(1)独立驱动程序
可以将驱动程序编写成同时包含MDD和PDD层的独立驱动。独立驱动的代码应当包括中断服务例程和平台相关处理函数。使用独立驱动的好处在于可以省去MDD和PDD层驱动之间的信息传递,这一点在实时处理中非常重要。另外,如果设备的操作和MDD驱动层的接口描述相吻合,可以使用独立驱动程序提高处理性能。
(2)层次型驱动
层次型驱动分为两层,较上层的Model Device Driver(MDD)和比较下层的Platform Dependent Driver(PDD)。MDD实现的是和平台无关的功能,它描述了一个通用的驱动程序框架。而PDD是和硬件以及平台相关的代码组成。MDD调用PDD中特定的接口来获取硬件相关的信息。当使用层次型驱动的时候,一般只需要基于相近的样列驱动程序,针对特定的硬件修改PDD程序,MDD建立的框架可继续使用。由于层次间接口的层层调用以及消息的传递,使得处理速度相对独立驱动程序要慢,因此在时间要求苛刻的环境下,层次型驱动显得不是很适合。
一般MDD将完成以下任务。
n 连接PDD层,并且定义它要使用到的Device Driver Service Provider Interface(DDSI)函数集;
n 向设备管理器提供Device Driver Interface(DDI)接口集;
n 处理复杂的事件,如中断等等。
每一种MDD驱动都处理不同种类的设备。DDI是由MDD层驱动以及独立型驱动提供给设备管理器的一组接口集。DDSI是由PDD向MDD层提供的接口集。公司的设备可以用同样的DDI。
在开发过程中,MDD层驱动是不需要被修改的。微软公司不保证被修改的MDD能在系统中正确运行的。和MDD层驱动不同的是,PDD层驱动必须被修改成和特定硬件相匹配的代码。程序员可以自己开发一个PDD程序,多数情况下建议开发者在Platform Builder提供的样例驱动程序上进行修改。例如,Platform Builder提供了Wavedev驱动程序,它的代码位于%WINCEROOT%\public\common\oak\drivers\WAVEDEV下,这是一个容易理解的流接口层次型驱动程序。此样例audio驱动程序仅提供了播放及录音功能,只提供播放功能的结构框架,播放功能和音频设备的交互还需要PDD层来解决。
通常只有OEMs才会对本机设备驱动程序进行修改,其他自由设备生产商由于只提供附加的硬件设备,对本机设备驱动程序不会有过多涉及。因此下面的本机设备驱动程序面向OEMs。
微软公司为每一种本机设备驱动程序设了一套custom接口。在此基础上,微软公司还为相同类型的设备驱动设计出了一组标准的接口。这样,Windows CE操作系统就能按照标准来操作同一类型的设备驱动,而不需要过多地去了解它们之间的硬件区别。如很多移动设备上都使用LCD来作为显示器,其中就有很多不同的LCD模块可以被使用到Windows CE的系统中。
Windows CE中提供了如下本机设备驱动程序实例。
n Display
n Battery
n Keyboard
n Touch screen
n Notification LED
如果在目标设备上,有以上列出之外的设备需要本机设备驱动程序,程序员就应当自己开发一套本机设备驱动程序。如果正在开发的目标系统上有上述的设备,开发人员应当考虑修改PlatformBuilder提供的样例驱动程序,尽可能不要重新编写驱动程序。样例驱动程序经过了Microsoft的测试,这比重新编写驱动程序省去一些验证上的麻烦。
n 流接口驱动有一套标准的接口,这和本机驱动是不一样的。
n 对于I/O设备来说是非常适合的。
n 操作接口和文件系统API十分类似,比如ReadFile,IOControl等。
n 应用程序可以和流接口驱动进行交互,并且可以把流驱动当成文件来操作。
流驱动与驱动接口、提供设备的种类无关,因为这组接口有统一的接口规范。对于需要数据流的设备来说,这种驱动是十分适合的,如串口就是个典型的例子。可以把使用流驱动的设备近似地看作是文件,这样可以通过文件系统API来操作设备,如ReadFile,IOControl。由于采用了文件系统的API,使得驱动程序能通过文件系统进行访问,这点和独立驱动程序是不同的。
这种将设备看成是文件的做法在很多操作系统上都比较常见。包括Windows桌面系统和类Unix的操作系统。例如,开发Windows驱动的程序员通常将打印机设备贯以LPTx:前缀,窗口被贯以COMx:前缀,这些都是特殊的文件名。
图9-2是流驱动程序在整个系统中的结构示意图。
图9-2 流驱动程序在系统中的结构示意图
图9-2显示了作为本机驱动而存在的流驱动程序,在启动时被设备管理器所加载。上节9.1.1介绍过,一般本机驱动是指custom接口的驱动程序,但是流驱动也可以成为本机驱动,例如串口。
如图9-2所示,流驱动通过文件系统API来和应用程序交互,同时又通过流接口接受设备管理器的管理。无论流驱动管理的是本机设备还是动态加载的设备,它们自身是在启动时被加载还是启动后由设备管理器动态加载,这和系统中其他模块的交互模型是一样的。
实现流驱动程序大致需要完成以下步骤。
(1)选择代表设备的文件名前缀;
(2)实现驱动的各个入口点;
(3)建立.DEF文件;
(4)在注册表中为驱动程序建立表项。
为了说明如何实现流驱动程序,我们将以platform builder提供的一个样例程序为参考来介绍。这个程序位于WINCE420\PLATFORM\SA11X0BD\DRIVERS\PWRBUTTON目录下,它是SA110XBD开发版上的power button驱动程序。
以下是创建流驱动的具体步骤。
(1)首先确定设备名的前缀。前缀非常重要,设备管理器在注册表中通过前缀来识别设备。同时,在流接口命名时,也将这个前缀作为入口点函数的前缀,如果设备前缀为XXX,那么流接口对应为XXX_Close,XXX_Init等。
(2)实现流接口的各个入口点。所谓入口点是指提供给设备管理器的标准文件I/O接口。
表9-1是对这些接口的介绍:
表9-1 接口的介绍
接 口 名 |
功能描述 |
XXX_Close |
关闭hOpenContext参数指定的设备上下文 |
XXX_Deinit |
通知设备管理器回收设备初始化时分配的资源 |
XXX_Init |
通知设备管理器为设备初始化时分配资源 |
XXX_Open |
打开设备,这个接口可以由应用程序直接调用createfile,然后通过文件系统映射为XXX_Open |
XXX_IOControl |
I/O控制指令 |
XXX_PowerUp |
设备加电时,此接口会被自动调用,可以在这里分配资源等 |
XXX_PowerDown |
如果设备能由软件控制断电,则在设备断电前,设备管理器会调用这个接口做些安全性检查 |
XX_Read |
从打开的设备文件中读取数据 |
XXX_Seek |
文件定位,如果设备支持的话 |
XXX_Write |
写数据到设备文件 |
在样例提供的驱动代码中,WINCE400\PLATFORM\SA11X0BD\DRIVERS\PWRBUTTON
\pwrbuttonpdd.c可以找到以上对应的入口点。
(3)建立*.DEF文件。在这个例子中,文件名为PWRBUTTON.DEF,DEF文件中定义了DLL要导出的接口集。流驱动大多是以DLL形式存在的,所以应将DLL和DEF的文件名统一起来。如PWRBUTTON.DEF,PWRBUTTON.DLL。
下面是PWRBUTTON.DEF文件的内容:
;Copyright (c) Microsoft Corporation. All rights reserved.
/* Copyright _ 1999 Intel Corp. */
LIBRARY PWRBUTTON
EXPORTS
PWR_Init
PWR_Deinit
PWR_Open
PWR_Close
PWR_Read
PWR_Write
PWR_Seek
PWR_IOControl
PWR_PowerDown
PWR_PowerUp
PWR_PowerHandler
PWR_DllEntry
如上代码所示,EXPORTS标签下,列出了一系列的接口,它们都是以PWR为前缀。
(4)在注册表中建立驱动程序入口点,这样设备管理器才能识别和管理这个驱动。
下面是power button驱动在注册表中的信息:
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\PWRBUTTON]
"Prefix"="PWR"
"Dll"="PwrButton.Dll"
"Order"=dword:2
"Ioctl"=dword:4
此外,注册表中还能存储额外的信息,这些信息可以在驱动运行之后被使用到。DLL项是设备管理器在加载驱动时需要的DLL名称;Prefix代表了设备前缀;ORDER是驱动程序被加载的顺序。
(1)XXX_Open入口
用于读/写打开一个设备文件。当应用程序调用CreateFile的时候,文件系统会自动调用本接口,打开一个已经存在的设备文件。当这个接口被调用的时候,设备驱动可以向设备管理器申请分配资源,并且为读/写文件做好准备。
下面是接口的原形:
DWORD XXX_Open( DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode );
参数解释:
hDeviceContext
指向XXX_Init返回的设备句柄(上下文)。
AccessCode
打开设备的权限描述符,这些权限包括读设备、写设备、读/写等。
所谓的设备上下文指的是代表图形设备接口graphics device interface(GDI)的数据结构。它包含了在特定区域内设备显示图象的信息。设备上下文包含了图形对象(例如pen、brush、字体等),不断地修改和调用它们,从而达到显示不同图象的效果。
(2)XXX_Close入口
在关闭设备的时候被操作系统调用。对应于CloseHandle接口。
BOOL XXX_Close( DWORD hOpenContext );
参数解释:
hOpenContext
指向XXX_Open返回的已经打开的设备句柄(上下文)。
应用程序可以调用CloseHandle来关闭正在使用的流驱动程序,然后设备管理器会相应地调用本接口来关闭设备,当设备关闭后hOpenContext描述的设备句柄将不再有效。
(1)XXX_Init入口
XXX_Init要完成以下任务。
n 在驱动被系统加载时,本接口被调用;
n 初始化需要的资源在本接口处理中被分配;
n 创建内存映射。
下面是接口的原型:
DWORD XXX_Init( DWORD dwContext );
参数解释:
n dwContext
指向一个字符串,它描述了注册表中的一个流设备接口。
当调用设备ActivateDeviceEx函数后,设备管理器自动调用这个函数。当用户激活一个新的设备时,如插入USB设备后,当总线自检时,设备就会被激活,这个接口就会被调用,这个接口是不允许应用程序直接调用的。
当这个接口的处理结果返回时,设备管理器就在注册表中寻找驱动的Ioctl子键。如果这个子键存在,设备管理器将调用XXX_IOControl接口将dwCode参数传入驱动入口点。
(2)XXX_Deinit入口
在驱动被系统卸载的时候,本接口将被调用,它将释放所有占用的阻援,并且停止IST。
下面是接口的原型:
BOOL XXX_Deinit( DWORD hDeviceContext );
参数解释:
hDeviceContext
指向设备上下文的句柄。这个句柄应该是由XXX_Init返回的。
当程序调用DeactivateDevice时,设备管理器将自动调用本接口。流接口将释放全部它申请的资源,并且停止设备的运行。
(1)XXX_Read入口
当应用程序直接调用ReadFile函数时,设备管理器将调用这个接口。
下面是接口的原型:
DWORD XXX_Read( DWORD hOpenContext, LPVOID pBuffer, DWORD Count );
参数解释:
hOpenContext
指向XXX_Open接口返回的设备上下文。
pBuffer
指向缓冲区,这个缓冲区将用来存放从设备中读出的数据,以字节为单位。
Count
指定要从设备读取多少字节的数据存入pBuffer指向的缓冲区中。
这个从指定的设备中读取指