原地址:http://blog.csdn.net/armeasy/article/details/4965710
许多人担心CE6驱动的向后兼容性。在CE6上,应用程序和OAL可以比较良好的兼容,但驱动就比较难。驱动在移植到CE6上必须做一定的修改,原封不动的放到CE6上运行是不太可能的。
驱动需要修改的主要原因:
1、 API的差异
2、 内存传递
3、 Buffer异步访问
4、 用户层接口处理
CE6驱动的最大差异在于内嵌指针和数据传递,这个在《Memory marshalling in Windows CE》有详细描述。有2个主要修改点:
1、 找出所有代码有映射函数,如MapCallerPtr和MapPtrToProcess,改为CeOpenCallerBuffer / CeCloseCallerBuffer。
2、 找出SetKMode、SetProcPermissions的地方,有异步访问的,修改为CeAllocAsynchronousBuffer / CeFreeAsynchronousBuffer。
其次查找UI相关的函数,内核中不允许驱动运行UI相关功能(显示UI)。CE6中,几乎所有驱动都运行在内核中。即使是用户态驱动,最好也使用kernel UI handling方式来处理UI。CE6中,驱动一旦使用UI相关的功能,就会切入到一个用户态的DLL。所有的资源、shell call等,都会带入到此DLL中。使用CeCallUserProc会较为方便。
BOOL CeCallUserProc(
LPCWSTR pszDllName,
LPCWSTR pszFuncName,
LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize,
LPDWORD lpBytesReturned);
这个函数类似调用IOCTL来得到LoadLibrary 和GetProcAddress的整合功能。当驱动在内核态调用此函数,udevice.exe实例中将会加载一个DLL。如果驱动在用户态调用此函数,也会在同个udevice.exe实例中加载此DLL,那么用户态和内核态驱动,在使用这个函数时候没有什么区别了。
CeCallUserProc和IOCTL之间最大区别在于,CeCallUserProc不允许内嵌指针。传入数据必须放在inbuffer参数中,而 传出数据只能放在outbuffer中,而不能通过内嵌指针再获取数据。问题是当内核调用用户层代码时,用户层无法同个CeOpenCallerBuffer或其他方法获取内核内存的大小。所以用户态不允许访问内核态的内存。
还有,当使用新的内存重建函数和CeCallUserProc,修改驱动时。最好注意是否需要做安全备份和异常处理,如上文所提。现在驱动都在内核态下,必须要保证系统的安全和稳定。
用户态驱动:
CE6具有用户态驱动进程,udevice.exe。用户态驱动和内核驱动一样,应用层可以使用ActivateDevice(Ex)和DeactivateDevice函数。设备管理器会读取注册表来判断驱动,是否需要运行在用户态。可以使用注册表指定udevice.exe的ID值, 让同一个进程加载多个用户态驱动。
例如,一个用户态驱动的驱动组ID为3,那么多个驱动都可以加载到这个组中。在CE6 %_WINCEROOT%/public/common/oak/files/common.reg中注册表,可以看到驱动组是怎样配置,驱动是怎样归纳到驱动组中的。如下:
[HKEY_LOCAL_MACHINE/Drivers/ProcGroup_0003]
"ProcName"="udevice.exe"
"ProcVolPrefix"="$udevice"
; Flags==0x10 is DEVFLAGS_LOAD_AS_USERPROC
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/Ethman]
"Flags"=dword:12
"UserProcGroup"=dword:3
[HKEY_LOCAL_MACHINE/Drivers/Console]
"Flags"=dword:10
"UserProcGroup"=dword:3
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/SIP]
"Flags"=dword:10
"UserProcGroup"=dword:3
如果你不配置这个,那么会单独由一个udevice来加载此驱动。系统将会有多个udevice进程。
设备管理器会创建一个转接服务来协助用户态驱动。转接服务负责加载udevice,加载特殊卷标,注册文件系统。应用程序和用户态驱动,通过转接服务进行buffer marshalling,来相互通讯。同时转接服务也会帮助用户态驱动做用户态上无法实现的工作,如映射物理内存等。
我们希望用户态和内核态驱动能达到完全一致,但内核态总是比用户态有更多的权限。不断改进的内核功能,将会使内核态驱动更难移植为用户态驱动。
另外,如前文所提。用户态驱动不能异步的回写指针参数,甚至可以认为,用户态不能异步访问caller的内存。最好把需要异步访问的驱动放到内核态,或者修改框架,让驱动不要异步访问caller。
还有,用户态驱动不能从内核中获取内嵌指针,CeCallUserProc也不支持内嵌指针。如果驱动需要从内核态获取内嵌指针,那么只能把此驱动放到内核中运行。或者修改驱动,不使用内嵌指针,而是调用CeCallUserProc让内存传递通过简单的in/out buffer来传递。
有些函数在用户态是必须注意使用的,如VirtualCopy和类似的MmMapIoSpace。用户态程序不允许使用VirtualCopy,但是用户 态能通过转接服务来实现这功能。转接服务能代替用户态驱动来调用VirtualCopy,但是前提是转接服务要知道这些地址是可以访问的。在驱动的注册表 入口,键值IOBase和IOLen来指出地址的位置和大小。当驱动使用VirtualCopy来,转接服务会检查这些键值,确保驱动能正常访问这些物理 地址。下面是串口驱动的例子:
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/Serial]
"IoBase"=dword:02F8
"IoLen"=dword:8
如果只有一块地址需要访问,使用dword类型。如果是多块地址,那么使用multi_sz类型。
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/Serial]
"IoBase"=multi_sz:"2f8","3f6"
"IoLen"=multi_sz:"8","2"
由于这些地址只能被有特权的应用程序访问,那么需要确保这些地址不能被非特权程序访问。
用户态驱动不能调用以下函数:
1、 VM虚拟内存函数:VirtualCopy[Ex], LockPages[Ex], CreateStaticMapping
2、 中断函数:InterruptInitialize, InterruptDone, LoadIntChainHandler
3、 不能直接使用IISR,需要通过转接服务来做GIISR。
4、 OAL层的IOCTL不能直接使用。
禁止从用户态驱动回调任何进程。你不能在总线驱动中回调一个内核态的client驱动。如果一个总线驱动放到用户态运行,只能把总线上的client驱动也放到用户态中。那么这个总线上所有的client驱动,都必须和总线驱动放到同一个udevice进程中。
有些OEM厂商可能会把一些OAL IOCTL和函数,通过编写特有的转接服务,让内核态驱动提供给用户态使用。值得注意的是,通过内核态驱动来公开这些功能,实际上是公开了微软特意封装的内容,最好不要这样使用。