wince流驱动入门讲解

 转自: http://wenku.baidu.com/view/e851a106ba1aa8114431d98a.html

第一章 驱动的准备 
1. 驱动程序完成以下功能: 
 对设备初始化和释放; 
 把数据从内核传送到硬件和从硬件读取数据; 
 读取应用程序传送给设备文件的数据和回送应用程序请求的数据;  检测和处理设备出现的错误。  
2. 整个硬件系统资源在驱动程序面前是赤裸裸的,驱动可以使用所有系统资源,编写驱动程序时我们必须格外小心驱动代码的边界条件,确保它们不会损坏整个操作系统。  
3. WinCE毕竟是一个嵌入式系统,有其自身的特殊性,为了提高运行效率,所有驱动皆为动态链接库,驱动实现中可以调用所有标准的API。而在其他Windows系统中可能的驱动文件还有.vxd,.sys和动态链接库。  
4. Windows支持的驱动: 

 1)虚拟设备驱动程序(Virtual Device Driver):Windows3.1(Windows95/98/Me)  

2)内核模式驱动程序(Kernel Mode Driver):Windows NT 

 3)Win32驱动程序模型(Win32 Driver Mode):从Windows98开始使用。 
其中WDM是目前主流,然而在WinCE系统中,由于硬件资源有限和嵌入式系统的特点,对其的支持非常有限。  


第二章 WINCE驱动 

1.WINCE驱动模型 
目前Windows CE提供了4种设备模型,其中2种专门用于Windows CE 模型,另外2种模型来自于其它的操系统,如图1所示: 




 

2.我们的工作 
为了帮助开发者快速地开发Windows CE驱动程序,微软在Platform Builder中提供了大量的驱动程序例源代码,同时,芯片厂商或OEM厂商有时也提供一些设备的驱动程序源代码,这些驱动程序源代码在多数情况下可以直接拿来使用,但是在少数情况下需要开发者根据自己的设备硬件特性做一些移植的工作,修改例源代码,重新编译和调试驱动程序。移植工作虽然没有像开发一个全新的驱动程序那样富有挑战性,但它仍具有相当大的难度,其原因如下: 
移植工作仍然要求开发者具有良好的软、硬件基础,熟悉驱动程序的基本开发和调试方法,并要求具有一定的开发环境和测试手段。 
移植工作仍然需要了解驱动程序的架构,需要确切知道驱动程序对外暴露哪些接口,微软提供了哪些接口,还必须实现哪些接口等。 
对于同一设备的驱动程序,其源代码往往位于Platform Builder多个不同的安装目录,移植工作首先需要找出所移植驱动程序的所有源代码的位置。 
移植工作需要在所移植驱动程序的所有源代码中区分出与硬件有关的代码和独立于硬件的代码,熟悉每个软件模块的大致功能,找出需要更改的与特定硬件有关的代码,并详细分析这些代码。 
大部分驱动程序的代码放在目录%_WINCEROOT%\public\COMMON\oak\drivers\下,这些驱动程序都是与平台无关的。此外,对于不同的平台,在BSP目录中也有一些驱动程序的代码,它们在%_WINCEROOT%platform\<BSP Name>\src \drivers\中,这些驱动都是与平台相关的。 
移植工作所修改的源代码有可能仅仅只有几十行甚至几行代码,但在修改之前却需要花费大量的时间了解驱动架构、熟悉驱动接口、分析源程序代码、找出需要修改的位置。本质上讲,移植与从头开发一个驱动的差别仅仅在于少写了很多程序。省去了编写这部分程序
外部驱动模型 
流接口驱动程序 本机驱动程序 
NDIS驱动程序 
USB驱动程序 
设备管理器 GWES  系统引导时间 
设备加载时间 应用程序加载时间 
基于Windows CE的驱动模型 
的时,但对驱动程序开发者的水平要求似乎并没有丝毫的降低。 


第三章 流接口函数 

流接口函数也称作流接口驱动程序的入口点,每个流接口驱动程序必须实现一组标准的函数,用来实现标准的文件I/O函数和电源管理函数,这些函数提供给Windows CE操作系统的内核使用。这些函数通常叫做流接口驱动程序的DLL接口。以下介绍几个主要的流接口驱动接口函数。 

(1)DWORD XXX_Open(DWORD hDeviceContext,   DWORD AccessCode,     DWORD ShareMode) 

参数: 

DWORD hDeviceContext,设备驱动的句柄,由XXX_ Init函数创建的时候返回。  

DWORD AccessCode,传给驱动程序使用的地址,这个地址跟读和写有关。   

DWORD ShareMode,共享模式,这个参数用于一些特殊的设备。例如一些PC卡的设备读或写的时候是否可以共享。 

返回值:返回驱动程序引用事例句柄。 
描述:这个函数用于打开一个设备驱动程序,当应用程序准备对某一个设备进行读或
写操作时,系统必须先执行CreateFile( )这个函数用于打开这个设备。这个函数执行以后系统才能够执行读和写操作。 

(2)BOOL XXX _ Close(DWORD hOpenContext) 

参数: 

DWORD hOpenCnntext,设备驱动的引用事例句柄,由XXX_Open创建。

 返回值:调用成功返回TRUE ,失败返回FALSE口 

描述:这个函数用于关闭一个驱动程序的引用实例。应用程序通过CloseHandle()来
调用这个函数,当执行完这个函数的时候驱动程序引用的事例,hOpenContext将不再有效。 

(3)DWORD XXX_ Init(DWORD dwContext) 

参数: 

DWORD dwContext,指向字符串的指针。通常这个参数都为一个流接口驱动在注册表内的设置。 
返回值:如果调用成功返回一个驱动程序的句柄。  
描述:当用户开始使用一个设备的时候,例如,当PC卡初始化的时候,设备管理器调用这个函数来初始化PC卡设备。这个函数并不是由应用程序直接调用的,而是通过设备管理器提供的ActivateDeviceEx( )函数来调用的。函数执行后如果成功则返回一个设备的句柄。 

(4)BDOL XXX_ Deinit(DWORD hDeviceContext)

 参数: 

DWORD hDeviceContext,由xxx_ Init创建时生成的设备句柄。 

返回值:调用成功返回TRUE ,失败返回FALSE。 

描述:当一个用户需要卸载一个驱动程序的时候,设备管理器调用这个函数来卸载这个驱动程序,应用程序不能够直接调用这个函数,设备管理器通过DeactivateDeviec()函数调用这个函数。 

(5)DWORD XXX_ Read(DWORD hOpenContext,     LPVOID pBuffer,   DWORD Count) 

参数: 

DWORD hOpenContext,  CreateFile()函数返回的句柄。      

LPVOID pBuffer,一个缓冲区地址用于从驱动读数据。       

DWORD Count,需要读缓冲区的长度。 返回值:实际读取字节的长度。 

描述:这个函数与ReadFile很相似,当一个流接口驱动程序已经被打开后,应用程序可以使用ReadFile( )函数对这个设备进行读操作,ReadFile()里面的hFile参数就是这个设备的引用实例句柄hOpenContext,而参数lpBuffer将传给pBuffer,用于表示要读/写缓冲区的地址。参数nNumberofBytesToRead将传送给Count,用于表示要读写缓冲区的长度。同样,返回的参数,如果操作成功则返回实际读/写的地址,如果操作失败则返回值为-1。 
(6)DWORD  XXX_Write (DWDRD hOpenContext, LPCVOID puffer, DWDRD  Count)  
 参数: 
DWDRD hOpenContext,由CreateFile()函数返回的句柄。 

LPVOID pBuffer, 一个缓冲区地址,用于从驱动写数据。               

DWORD Count,需要写缓冲区的长度。 

返回值:实际写入字节的长度。 

描述:当一个流接口驱动程序打开以后,应用程序可以使用WriteFile ()函数进行写操作。 

(7)BOOL XXX_ IOControl (DWORD hQpenContext.  WORD dwCode, PBYTE pBufIn, DWORD dwLenIn,   PBYTE pBufOut,  DWORD dwLenDut,  PDWORD pdwActualOut  ) 

参数: 
DWORD hOpenContext,由CreateFile()函数返回的句柄。 
WORD dwCode,特殊的WORD 型用于描述这次IOControl操作的语义,一般这个都由用户自己定义。 

PBYTE pBufIn,缓冲区指针指向需要传送给驱动程序使用的数据。       

DWORD dwLenIn,要传送给驱动程序使用数据的长度。 

PI3YTE pBufOut,缓冲区指针指向驱动程序传给应用程序使用的数据:       

DWDRD dwLEnOut,要传送给应用程序使用数据的长度。 

PDWORD pdwActualOut,DWORD型指针用于返回实际处理数据的长度。 

返回值:调用成功返回TRUE,调用失败返回FALSE。  

描述:这个函数通常用于向设备发送命令。应用程序使用DeviceIOControl函数来通知操作系统调用这个函数。通过参数dwCode来通知驱动程序要执行的操作。这个函数扩展了流接口驱动程序的功能 
(8)VOID XXX_ PowerDown (DWORD hDeviceContext) 

参数:

DWORD hDeviceContext,由XXX_ Init创建时生成的设备句柄。 

返回值:无返回值。 


(9)VOID XXX_PowerUp ( DWORD hDeviceContext ) 

参数:

WORD hDeviceContext,由XXX_ init创建时生成的设备句柄。 

返回值:无返回值。 

描述:PowerDown和PowerUp这两个函数通常都必须要硬件的支持才能够有效,也是说相关的硬件必须支持PowerDown和PowerUp这两个模式。

 (10)VOID XXX_Seek(hDeviceContext,   Long Amount,   WORD Type)

 参数: 

hDeviceContext,由XXX_ init创建时生成的设备句柄。     

Long Amount 定义要移动的设备数据的指针的字节数     

WORD Type  定义数据指针的起始点 

返回值:无返回值 

描述:当一个应用程序调用SetFilePointer函数移动设备数据指针时,操作系统会调用XXX_Seek函数。如果一个设备可以被多次打开,这个函数只修改由hDeviceContext定义的设备实例的数据指针。


第四章 最简单的流接口函数 
对于一个驱动我们要认清楚里面到底有哪些文件,他们的作用又是干什么的呢?下面以 SimpleDriver为例,进行第一个简单流接口驱动的讲解。 

* 1.Makefile文件 
这里的Makefile文件请 不要和其他环境下(GCC,VS2005)的Makefile文件弄混,它是BSP里面的Makefile。Windows CE中的Makefile比较特别,它包含对所有项目都通用的配置信息。 其内容很简单,只有一句话: 
!INCLUDE $(_MAKEENVROOT)\makefile.def 
当build.exe查找dirs和source文件之后,它就会设置一个内部环境变量。这个环境变量可以被Nmake.exe传递给编译器、连接器或其他工具。 
* 2.source文件 
source也是一个文本文件,它为子目录中的源代码设置了不少宏定义。  
TARGETNAME=SimpleDriver RELEASETYPE=PLATFORM TARGETTYPE=DYNLINK 
TARGETLIBS=$(_COMMONSDKROOT)\lib\$(_CPUINDPATH)\coredll.lib     DEFFILE=$(TARGETNAME).def DLLENTRY=DllEntry SOURCES=SimpleDriver.c 
以上是simpledriver里面source的内容,具体解释如下: 
TARGETNAME=SimpleDriver; 指定生成最终生成的.exe,.lib,.dll文件的名称,这里是SimpleDriver.dll 
RELEASETYPE=PLATFORM; 它设置两种旗标:RELEASEDIR和RELEASELIBDIR,用于指定编译生成二进制和库文件存放的目录。默认情况下,为目标生成的二进制和库文件存放在目录%_PROJECTROOT%\oak下,这里存放在D:\WINCE600\PLATFORM\Mini2440\target\ARMV4I\retail目录下。 
TARGETTYPE=DYNLINK;这个宏定义指定构建文件的最终类型,可以把TARGETTYPE类型设置为以下四种类型中的任意一种。 1)MANAGED_EXE; 2) MANAGED_DLL; 3) MANAGED_WINEXE; 4) MANAGED_MODULE;  这里设置的最终类型为dll。 
TARGETLIBS=$(_COMMONSDKROOT)\lib\$(_CPUINDPATH)\coredll.lib    ; _COMMONSDKROOT等价于\WINCE600\public\common\sdk  
它指定了额外的库文件(.lib)和目标文件(.Obj)链接为目标可执行文件(.exe或.dll).这里将 coredll.lib 链接生成最终的目标文件SimpleDriver.dll DEFFILE=$(TARGETNAME).def; 它指定模块定义文件(.def)的名称,这里指定了模块定义文件的名称为SimpleDriver.def 
DLLENTRY=DllEntry; 它为一个DLL文件指定DLL的入口函数,此时TARGETTYPE被设置为DYNLINK。如果 DLLENTRY对应的值没有被设置时,那么 
DllMainCRTStartUp是DLL的C程序运行入口点。这里 DLLENTRY的入口函数被指定为DllEntry,因而,上面的 TARGETTYPE被设置成DYNLINK; 
SOURCES=SimpleDriver.c; 它包含编译过程的文件列表,这些列表中包含汇编文件和源文件,这些文件的类型
有.cxx,.cpp,.c,.asm,.s,.src,.rc,.obj,.ire,.res,.odl,.tlb,.i,.cs,.resx等。这些文件编译之后可能是静态库文件(.lib),也有可能是动态库文件(.dll)。这里编译过程中需要用到的源文件有SimpleDriver.c,编译之后的生成SimpleDriver.dll的动态链接库文件。

 * 3.SimpleDriver.def文件 
.def文件定义了DLL的导出函数列表。 这里包括的内容如下: 
LIBRARY SimpleDriver  
EXPORTS SPL_Init SPL_Deinit SPL_Open SPL_Close SPL_Read SPL_Write SPL_Seek SPL_IOControl   SPL_PowerDown SPL_PowerUp 
主要是针对当前流接口函数,将相应的函数导出。

* 4. SimpleDriver.h 
这个而就不用多介绍了吧,主要是一些头文件的声明,定义等等   
* 5.SimpleDriver.c 
下面给出基本的代码,有些函数给出了空定义,方便以后实现,同时方便理解。 

* 6. platform.reg 
配置文件的编写。涉及到的文件
有.platform.bib,platform.reg,dirs,source,SimpleDriver.def,其中后面两个文件在前文已有绍,这里就以前两个配置文件为主。 
首先,从注册表说起,先简单的介绍一下注册表:Makeimg.exe使用.reg文件来 为CE镜像建立注册表并添加默认的键值。也就是说,在.reg中写入的注册表的键值会被默认地放入CE镜像的初始化注册表中。其中Platform.reg定义了目标设备硬件的注册表设置,Project.reg定义基于Windows CE项目工程的注册表设置。注册表键值的类型如下: 
1.       REG_SZ表示一个字符串类型,如reg_sz:”my string”。 
2.       REG_DWORD表示一个双字节类型,如dword:12345678(十六进制数)。

  3.       REG_MULTI_SZ表示多字符串类型,如multi_sz:“my string”,“my string”。

  4.       REG_BINARY 二进制类型。 
在实际应用中可以使用IF/ENDIF关键字来引入一个注册表设置块,通过设置一个环境变量或一个特殊的值来达到这个目的。 
为了包含一个注册表设置块,当一个环境变量没有被设置或者没有等于一个特定的值的时候,引入的注册表设置块的行尾应使用一个空格和“!”。 这里比较好找,一目了然,在目录
D:\WINCE600\PLATFORM\SMDKXXXX\Files\platform.reg下添加: 
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\SPL]    "Prefix"="SPL" 
   "Dll"="SimplDriver.Dll"     "FriendlyName"="Simple Driver" 

   "Order"=dword:0 //驱动安装顺序    "Index"=dword:0 //驱动名的数字部分 
注: Order 
给所有的驱动设置相对安装顺序。Order 为0的驱动先安装,依次安装Order为1,2,„的驱动。Oder可以让开发者保证有相到依赖关系的多个驱动以适当的顺序进行安装。 Index 
指定在文件系统中的驱动名的数字部分。在默认情况下,带有前缀COM的驱动在文件系统中分配名字为 COM1,下一个即为COM2。为了保证驱动总是安装成COM2,必须提供Index = 2。 
这样在WINCE启动时,就自动加载该驱动了。 

* 7.  platform.bib 
接着我们介绍platform.bib文件,关于bib文 件,这里做一个简要的介绍:二进制镜像文件构建文件(.bib)定义了哪个模块或者文件被包含到运行时的CE镜像中。在编译期间,makeimg.exe使若干个.bib文 件合并成ce.bib文件,然后,romimage.exe使用ce.bib决 定哪些文件应该被打包进运行时CE镜像中,它同时还使用ce.bib来决定如何加载模块和文件到CE镜像(下载到目标设备的CE镜像)所在的内存中。 
按照功能划分,.bib文件可以分为如下几种类型。 
(1)                 Platform.bib。Platform.bib位于目录
D:\WINCE600\PLATFORM\SMDK6410\Files,它包含硬件平台相关的模块和文件,如目标设备的驱动文件。这些模块和文件是CE镜像的入口,比如.exe文件,如波形音频文件(.wav)等。 

(2)                 Project.bib。Project.bib 位于目录 
D:\WINCE600\PUBLIC\CEBASE\OAK\FILES,如果我们新建一个工程项目(OSDesign1)则这个工程位于目录:
D:\WINCE600\OSDesigns\OSDesign1\OSDesign1\Wince600\SMDK6410_ARMV4I\OAK\files下。Project.bib文件定义与创建CE镜像的工程相关的模块,如果在OSDesign1中创建了一个自己的模块或者应用程序,那么就要把它们添加到Project.bib文件中的MODILES部分。 
(3)                 Common.bib。Common.bib位于目录
D:\WINCE600\PUBLIC\COMMON\OAK\FILES下,它定义了CE镜像文件包含的通用显示驱动和核心系统模块。 

(4)                 Config.bib。Config.bib位于目录
D:\WINCE600\PLATFORM\SMDK6410\Files下,它定义了ROM和RAM的配置信息。它同样包含了CE镜像文件的MEMORY和CONFIG部分。Config.bib的MEMORY部分定义了运行时CE镜像内存分配表,指定了名称、地址、大小和MEMORY区域的类型。


 .bib文件可以被分为四个部分,分别是MEMORY、CONFIG、MODULES和FILES。下 面将分别说明这四个部分各自的含义。 
(1)       MEMORY。定义可用的物理内存,包括起始地址、大小和内存类型。 (2)       CONFIG。定义romimage.exe输出的配置选项。默认情况下,这个区
域是在config.bib文件中。不过,也并不是.bib文件中必须要包含CONFIG部分。 (3)       FILES。指定放在CE镜像中的文件列表。 
(4)      MODULES。 指定放在CE镜像 总的模块列表,包括EXE和DLL文件,
与FILES的 区别是放在MODULES中的文件通常是代码文件,并且构建系统时不会压缩这些文件。 
这里我们主要介绍Modules部分。 
Modules部分指定了哪些基于Windows CE的模块包含到CE镜像中,以及如何给加载到config.bib文件中的MEMORY部 分建立内存表。这个部分可以包含200个模块,这些模块有源代码和数据两个部分组成。 
MODULES的语法格式如下: 
;   Name  Path  Memory block    Section override Memory Type ;   ---  -----  ------------      ---------    -------- 
各参数之间用空格分隔。 
NAME:这个参数指定了MODULES模块的名称。通常情况下,它就像被路径引用的文件名称一样。 
PATH:指定要打包进CE镜像的MODULES模块的完整路径。 
MEMORY BLOCK:这个参数指定romimage.exe加载目标模块到内存区域的ramimage(镜像)部分。这个内存位于config.bib文件中memory 部分指定的某一段内存。 
SECTION OVERRIDE:这个参数的设置时可选的,它可以为modules、files或者空。如果设置了这个参数,那么构建系统就会根据它来决定这一项是modules还是files。 
TYPE:这个参数指定文件的类型,主要有以下几种类型,在实际的使用中可以选用其中的一种或者多种组合。 
1.       S:定义一个系统文件 

2.       H:定义一个隐藏文件 
3.       R:压缩资源,只应用于MODULES部分

 4.       C:如果应用于一个模块,则压缩全部内容

 5.       D:运行时不允许调试。 
6.       N:标记一个模块为不可信任的,只应用MODULES部分。 
7.       K:指定romimage.exe必须修正模块到一个内核地址。在这个过程中,romimage.exe分配一个固定的虚拟地址给DLL,设置了此标志的模块只可以被loadKernelLibrary()函数加载。

 8.       P:指定romimage.exe禁止在头文件中检查指定的CPU类型。这个旗标只用于资源dll,可以在一种CPU伤 编译,在不同CPU上使用。 

9.       M:表示对此页禁止按序调页。 

10.   U:表示不压缩此文件。 在目录D:\WINCE600\PLATFORM\SMDKXXXX\Files\platform.bib文件中有如下定义: 
;   Name                 Path                    Memory Type ;   --------------     ----------------         ------------------ ;-----------Simple Driver (caichang714@hotmail.com)------------------- simpledriver.dll    $(_FLATRELEASEDIR)\simpledriver.dll    NK     SHK ; @CESYSGEN ENDIF CE_MODULES_DEVICE 
;-------------------------------------------------------------------- 其 中,_$(_FLATRELEASEDIR)\ 指的是生成的镜像所在目录D:\WINCE600\OSDesigns\SMDKXXXX\SMDKXXXX\RelDir 
结合上面的说明,我们可以知道,上面的语句的意思是将编译生成的simpledriver.dll模块加载到CE运行时镜像NK中。它的文件属性是系统文件,隐藏文件和内核模块,由romimage.exe分配一个固定的虚拟地址给simpledriver.DLL。 
接 着在D:\WINCE600\PLATFORM\SMDKXXXX\Src\Drivers目录下找到dirs文件,在里面添加:SimpleDriver\ 
 最后点击build或者在dos环境下 build -c 
OK! 
到这里点击编译就可以了。这个新驱动程序的动态链接库将会被编译进内核。当调用CreateFile()函数(第一个参数是SPL)时就搜索注册表,设备管理器可以找到这个驱动,驱动的入口点可以用到其他程序。

 
 第五章 需要注意的事项 
1.驱动程序的内存访问及函数入口 
当要在内核模式下执行代码时,必要时需访问整个物理内存空间,这一点与用户模式下执行是截然不同的。因为一个在用户模式运行的IST需要通过访问一个物理内存块来与一个设备进行交互,所以必须要把这个物理内存块映射到IST运行的地址空间。把一个物理内存块映射到执行IST的地址空间有两种方法:VirtualAlloc()申请虚拟内存块和VirtualCopy()绑定物理内存块与虚拟内存块 
1) 申请一个虚拟内存块 LPVOID VirtualAlloc ( LPVOID lpAdress,         //申请虚拟内存块的起始地址 
  DWORD dwsize,          //申请内存块的大小,以B为单位,不能设为0  

DWORD flAllocationType,   //申请的类型,分为COMMIT和RESERVE  //COMMIT: 在内存或者磁盘上为指定的页面区域分配物理存储   //RESERVE:保留一定范围的IST进程的虚拟地址空间不分配物理存储  
DWORD flProtect          //访问权限 
);

如果调用成功,返回分配的首地址,调用失败,返回NULL 你可以通过GetLastError函数来获取错误信息。 
注:VirtualAlloc()函数对虚拟内存块的申请分为两步,首先是执行保留动作,它在此期间做的工作只是在进程的虚拟地址空间内保留一段空间,并没有分配实际的物理内存,保留的虚拟内存并不能被应用程序直接使用。其次,执行提交动作后,这样后才真正的为虚拟内存块映射一段物理内存。执行成功之后,将返回一个内存块指针给用户。  
2) 一个物理地址块与一个虚拟内存块的绑定 BOOL VirtualCopy ( LPVOID lpvDest,  //绑定目的地址:虚拟内存块

 LPVOID lpvSrc,    //绑定源地址:物理内存块 

DWORD cbSize,   //要绑定的大小

 DWORD fdwProject  //访问权限 
)

返回值:成功返回TRUE,失败返回FALSE 
注:当虚拟内存所要的工作处理完成,需要调用VirtualFree()函数来释放此虚拟内存。 VirtualCopy()函数支持PAGE_PHYSICAL旗标,当映射驻留在512MB之外的物理内   存时,也就是在物理地址大于0x1FFFFFFF时,必须设置此标志。 
 
3)释放内存 
BOOL VirtualFree( LPVOID lpAddress, // 区域地址 
SIZE_T dwSize, // 区域大小,字节 

DWORD dwFreeType // 类型 
); 
LPVOID lpAddress, 要释放的页的区域的地址,如果dwFreeType指定为MEM_RELEASE且这个区域是被保留的话,那么这个地址就要指定为分配区域的首地址 
SIZE_T dwSize, 要释放页的大小,如果dwFreeType类型中包含了MEM_RELEASE,则dwSize必须为0 DWORD dwFreeType 类型说明 
MEM_DECOMMIT  消VirtualAlloc提交的页 
MEM_RELEASE   指定页,如果指定了这个类型,则dwSize应设置为0, 否则函数会调用失败 
返回值: 如果调用成功,返回一个非0值 调用失败,返回0  
4)BOOL WINAPI DllEntry(HINSTANCE DllInstance, INT Reason, LPVOID Reserved ) 
这个函数是动态链接库的入口,每个动态链接库都需要输出这个函数,它只在动态库被加载和卸载时被调用,也就是设备管理器调用LoadLibrary而引起它被装入内存和调用UnloadLibrary将其从内存释放时被调用, 因而它是每个动态链接库最早被调用的函数,一般用它做一些全局变量的初始化。 参数: 
  DllInstance:DLL的句柄,与一个EXE文件的句柄功能类似,一般可以通过它在得到DLL中的一些资源,例如对话框,除此之外一般没什么用处。 
  Reason: 一般我们只关心两个值:DLL_PROCESS_ATTACH与DLL_PROCESS_DETACH,Reason等于前者是动态库被加载,等于后者是动态库被释放。所以,我们可以在Reason等于前者是初始化一些资源,等于后者时将其释放。

2.MDD与PDD 

一个驱动程序通常会被分成硬件相关(PDD)与硬件无关(MDD)层两部分。 当然,这种分层不是必须的,只是采用这种分层以后可以少写很多代码,因为微软提供了很多驱动程序的MDD。即使CE中没有我们所写的驱动程序的样例,采用这种结构以后,当需要写第二个程序时,就可以重用它的代码,就可以提高开发效率。 
MDD是提供同类型的设备(比如串口)都会有的功能,这样PDD基本上就只有寄存器操作了。像串口的中断处理,Read/Write函数,其大部分代码都是在MDD中实现的,不同的串口实现中只需要提供一些实际操作寄存器的函数。不同的驱动程序,其MDD与PDD的接口不尽相同,所以,当我们面对一个具体的驱动程序时,需要查帮助弄清楚需要提供哪些函数  
3 XXX_Init函数的返回句柄 
通常,这个句柄是驱动程序自己保存数据的一个指针,我们在Init返回时告诉上层程序,以后上层调用其它函数(例如Open)时,会将这个值传入,这样,我们就可以访问自己的一些私有数据。当然,也可以返回一个任意的非0值。对于一个设备驱动程序,系统不用的层会有不同的句柄。我们在XXX_Init中返回的句柄保存在设备管理器中,别的程序中应 该是看不到的,而用CreateFile也会得到一个文件句柄,这个保存在哪我不知道。但和前者是不一样的。也就是说不同层的软件所关心的句柄也会不一样  

4.DEBUGMSG与RETAILMSG的区别 它们都是输出调试信息用的,区别是: 
DEBUGMSG只在DEBUG版中有效,RELEASE版中它被定义成了NULL。 RETAILMSG在DEBUG和RELEASE版中都可以输出。 
而且DEBUGMSG可以在运行时刻用DEBUZONE控制要不要输出信息。在ship build 时,RETAILMSG 和DEBUGMSG都无效  

5.物理基地址到虚拟地址的映射: 
   在进行硬件设计的时候,每个外围设备相对CPU都有一个物理基地址及一段可寻址的地址空间,驱动程序需要访问这段地址空间以和设备进行通信,所以,驱动程序必须将这个物理的内存块映射到IST(中断服务线程)正在运行的的地址空间(虚拟地址空间),完成从物理地址到虚拟地址的映射。 
     通过执行下列两步可以完成这个映射: 
(1) 保留一个虚拟内存块。为此,调用VirtualAlloc函数,并必须传递MEM_RESERVE
值作为这个函数的一个参数。 
(2) Windows CE分配内存并返回一个指向这个内存块的指针,然后需要将这个块与一个
实际的物理内存块对应。为此调用VirtualCopy函数,并定义PAGE_NOCACHE参数以便在这个块里存储的数据不能够被缓冲,这个标志迫使系统不优化读操作。 
 
6.基本调试方法 
一般驱动程序可以用DEBUG版来调试,也可以用输出调试信息的方法。我们一般用这两个函数输出调试信息:RETAILMSG和DEBUGMS,后者只能在DEBUG版中输出,而前者在RELEASE和DEBUG版中都可以输出,而且,可以在系统运行时刻根据Debug Zone选择让DEBUGMSG输出哪些调试信息。 驱动程序的调试一般可以分为以下几步: 
1.看驱动程序的DllEntry是否被调用。如果这个函数被调用,说明驱动程序的文件已经在CE的image中,而且与注册表中设置的文件名相同。 
2.看Init 函数是否被调用。如果它被调用,刚说明注册表设置正确。如果它没有被调用,一般是因为注册表中的Prefix设置与Init函数前面那三个字符不相同。或者def文件中没有定义Init函数。如果这个函数能够被调用,但驱动程序还是不能正确加载,请详细检查代码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值