打印

探索Windows CE 6驱动程序新特性

[复制链接]
5342|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
high|  楼主 | 2009-10-15 16:31 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
来源:何宗键

Windows CE 6驱动程序新特性之访问检查
访问检查是用来检查函数的调用者是否有足够的权限去访问传递给这个函数的内存。访问检查是很必要的,它可以防止恶意的应用程序利用驱动程序去完成需要特权才能访问的资源。设备驱动程序由于在Windows Embedded CE6.0中处于内核空间所以是一种特权程序,可以访问很多系统的资源。而工作在用户态的应用程序却不是。如果一个应用程序利用设备驱动程序去读写系统的内存,那么设备驱动程序实际上就相当于是授予了这个应用程序高的访问权限。所以在设备驱动程序中进行访问检查可以保护操作系统的内存不受恶意应用程序的破坏。
在Windows CE 5.0中,设备驱动程序是通过MapCallerPtr API来对指针参数和嵌套的指针参数进行访问检查的。
  • struct MyStruct { UCHAR *pEmbedded; DWORD dwSize; };  
  • // Windows CE 5.0 and prior versions
  • // In XXX_IOControl...
  • MyStuct *p = ( MyStruct*) pInput;  
  • g_pMappedEmbedded = MapCallerPtr(p->pEmbedded);  
  • // Fail if g_pMappedEmbedded == NULL ...

在Windows Embedded CE 6.0中,设备驱动程序只需要检查嵌套的指针参数是否有效就可以了。所不同的是在WindowsEmbedded CE 6.0中需要使用CeOpenCallerBuffer这个API来检查是否对于某一段内存,调用进程是否有访问权限。
  • // Now in the New CE Version
  • // In XXX_IOControl...
  • hr = CeOpenCallerBuffer((PVOID*)&g_pMappedEmbedded, pInput->pEmbedded, pInput->dwSize, ARG_I_PTR, FALSE);  
  • // Fail if FAILED(hr) == true
  • // When done with pointer...
  • hr = CeCloseCallerBuffer((PVOID)g_pMappedEmbedded, pInput->pEmbedded, pInput->dwSize, ARG_I_PTR );  

Windows CE 6驱动程序新特性之Marshalling
处于内核态的线程对位于用户空间的内存的访问可以分为两种方式。同步访问(SynchronousAccess)和异步访问(AsynchronousAccess)。同步访问是指,当访问这块内存区域时,处于内核态的线程是在调用者的上下文环境内。而异步访问正好相反,设备驱动程序所拥有的一个线程要访问的一块内存区域,而该区域正位于其他进程的地址空间中,那么这就是异步访问。
举个异步访问的例子,比如应用程序通过WriteFileAPI来向设备SD卡中写入一段数据,最后写的请求会通过操作系统内核发送到SD卡的设备驱动中,由设备驱动程序来写入数据。但是I/O操作是一个很慢的操作,为了使CPU达到高的利用率,内核往往会在这时重新调度一个线程开始执行,这样用户空间就可能发生了一次切换。这里说可能,是因为如果被调度的线程和当前的应用程序(也就是当前进程)处于同一进程空间的话,那么就不发生进程的切换,并且要写入的这段数据由于还是位于当前进程空间中,所以就是对于这块数据的访问就是同步的。反之,如果被调度的线程不处于当前进程的空间,那么就会发生一次进程的切换,新的进程会替换老的进程,这时要写入的这块数据区域对于设备驱动程序中的线程来说就是无效的,这也称为异步访问。
对于异步访问必须采用一种叫Marshalling的技术来处理被访问的数据区域。
在Windows CE 5.0中,所有的用户态进程共享底部的虚拟地址。所以当Slot 0中被替换为不同进程时,由于所有的进程的虚拟地址空间对于设备驱动程序来说都可见,只需要对指向数据块首地址的指针做一次偏移,就可以得到这块数据。
在Windows Embedded CE6.0中,每个用户态的进程都有自己独有的虚拟地址空间,每个进程的虚拟地址空间都是受到保护的。所以对一块内存做Marshall不再是简单的将指针进行偏移。这时可以采用两种方法来对一块内存做Marshall,一种方法是将这块内存拷贝一份这样就可以安全的访问,这种方法称为复制。另一种方法是用一个新的虚拟地址去引用所对应的物理,这样一来这块数据就分别被两个指针分别引用,这种方法称为别名。
Marshall一块内存区域需要区分同步访问和异步访问。在Windows CE5.0中,对于同步访问来说,不需要做多余的工作,只需要调用MapCallerPtrAPI来Marshall嵌套指针所指的地址即可。对于异步访问,线程对于每个Slot都有一定的访问权限,所以需要通过调用SetProcPermissions来先获得调用进程Slot的访问权限,然后调用MapCallerPtr来Marshall所要异步访问的内存。
  • // Windows CE 5.0 and prior versions
  • // In XXX_IOControl...
  •    SetProcPermissions(-1);  
  •    g_pMappedEmbedded = MapCallerPtr( p->pEmbedded );  
  • // Fail if g_pMappedEmbedded == NULL ...
在Windows Embedded CE6.0中,对于同步访问,通过设备驱动程序通过调用CeOpenCallerBuffer来Marshall嵌套指针所指的数据块的首地址,当这块数据使用完以后,设备驱动程序调用CeFreeCallerBuffer来释放Marshall所得资源。对于异步访问,假设设备驱动程序可以同步的访问一块内存,那么这时就可以通过调用CeAllocAsynchronousBuffer来Marshall这块内存用来做异步访问之用。在使用完之后,可以通过调用CeFreeAsynchronousBuffer来释放Marshall所得资源。
  • // Now in the New OS Version
  • // In XXX_IOControl after CeOpenCallerBuffer generates
  • // g_pMappedEmbedded...
  • hr = CeAllocAsynchronousBuffer((PVOID*)&g_pMarshalled, g_pMappedEmbedded, pInput->dwSize, ARG_I_PTR);   
  • // Fail if FAILED(hr) == true
  • // When done with pointer...
  • hr = CeFreeAsynchronousBuffer((PVOID)g_pMarshalled, g_pMappedEmbedded, pInput->dwSize, ARG_I_PTR);   
  • // Now call CeCloseCallerBuffer as usual...

Windows CE 6驱动程序新特性之用户模式下的驱动程序
在Windows Embedded CE6.0之前,设备驱动程序是加载在device.exe进程之中的,而device.exe与普通应用程序一样也是用户态的进程。所以每次应用程序希望能够与外设进行交互时,都需要通过操作系统内核转发请求到相应的驱动程序。这样一个请求就可以需要反复的进出内核多次,还需要在不同的进程间进行切换。这样做的好处和缺点同样的明显,优点是操作系统的稳定性得到了提高,不会因为某个设备驱动中的缺陷而使整个操作系统崩溃。但缺点是完成请求的效率太低。
在Windows Embedded CE 6.0中,设备驱动程序能够工作在用户模式或是内核模式两种不同的模式。由于在WindowsEmbedded CE6.0新的体系结构中将操作系统关键的部件如文件系统Filesystem.exe、设备驱动程序管理Device.exe等都移进了操作系统内核之中,驱动程序完成一个请求不再需要在不同的进程下进行切换,也不需要反复的进出内核,所以内核模式下的驱动程序完成请求的效率将会大大的提高。但是这样的效率提高也是要有代价的。内核模式下的驱动程序需要有很高的稳定性,任何一个错误都可能引起整个操作系统的崩溃。为了解决这个问题,WindowsEmbedded CE 6.0中还设计了另一类称为用户模式的设备驱动程序。
Windows Embedded CE6.0中设计了一个用户进程udevice.exe用来加载设备驱动程序。因为是被用户态的进程所加载,所以这类驱动程序就工作在用户空间,这类驱动程序就成为用户模式下的设备驱动程序。用户模式的驱动程序无疑将增加操作系统的稳定性,并且由于这类驱动程序工作在用户空间,所以能力有限,不能使用诸如VirtualCopy这样的特权API,也不能对系统中的硬件资源有任意的访问权限。
用户模式的设备驱动程序还有一个特点就是它们与内核模式的设备驱动程序具有高度的兼容性。一个好的用户模式的设备驱动程序的源代码不需要做任何的改动就可以做为内核模式的设备驱动程序被加载进内核空间。区分用户模式的设备驱动程序和内核模式的设备驱动程序标志就是设备驱动程序在系统注册表中的Flag值。当Flag具有DEVFLAGS_LOAD_AS_USERPROC(0x10)时,系统会将设备驱动程序加载成用户模式,如果没有该标志就加载成内核模式。
总的来说,用户模式下Windows CE6驱动程序新特性的设备驱动程序和内核模式下的设备驱动程序是很相似的。在有了用户模式设备驱动程序和内核模式设备驱动程序后,在OEM开发BSP的过程中,如果采用了某些第三方的,未经充分测试的驱动程序后。可以先将这些驱动程序做为用户模式下的设备驱动程序加载到用户空间,等到整个系统经过测试可以长时间稳定运行后,再将其转变成内核模式下的驱动程序加载到内核空间以提高整个系统的效率。

相关帖子

沙发
high|  楼主 | 2009-10-15 16:33 | 只看该作者
本帖最后由 high 于 2009-10-15 16:36 编辑

CE6 驱动: 你不得不知道的事情
来源:http://blogs.msdn.com/ce_base/ar ... u-need-to-know.aspx


许多人担心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和函数,通过编写特有的转接服务,让内核态驱动提供给用户态使用。值得注意的是,通过内核态驱动来公开这些功能,实际上是公开了微软特意封装的内容,最好不要这样使用。

使用特权

评论回复
板凳
high|  楼主 | 2009-10-15 16:34 | 只看该作者
本帖最后由 high 于 2009-10-15 16:36 编辑

Windows CE的内存地址重建
来源:

http://blogs.msdn.com/ce_base/ar ... -in-Windows-CE.aspx
本文描述CE6和之前的版本,在内存访问和传递的差异,阐述CE6与之前版本的不一致之处。BSP和驱动开发人员需要了解OS之间的变化。



首先给出client到server的指针传递的定义:

1、指针参数Pointer parameter:把指针作为函数参数传递

2、内嵌指针Embedded pointer:把指针存储在buffer中传递

3、访问权限检查Access Checking:检查调用进程有无访问buffer的权限

4、安全复制Secure-copy:备份buffer,防止调用者异步修改buffer的内容

5、同步访问Synchronous:当函数调用时, 在被调用线程中访问内存



指针参数:是把指针作为参数传递。例如:

ReadFile (hFile, pBuffer, dwBufferSize, ...);

pBuffer是函数ReadFile的参数,而且它是个指针。



内嵌指针:把指针被包含在一个指针参数里,传递给API。或者嵌套在其他内嵌指针中。例如

struct MyStruct  {

  BYTE *pEmbedded;

  DWORD dwSize;

};

DeviceIoControl (hFile, pMyStruct, sizeof(MyStruct), ...);

参数pMyStruct是一个指针参数,而pEmbedded包含在MyStruct结构体内,那么pEmbedded就作为一个内嵌指针传递给DeviceIoControl。



指针也可以通过其他方式传递,如存:存放在一片共享内存中;或者使用SetEventData函数,通过事件响应来获取。



访问权限检查:是检验函数的调用者,是否有足够权限去访问一块函数间传递的buffer。(访问权限检查不仅仅局限在内存,在此只定义权限检查为内存的权限检查)。驱动有许多权限,能够访问许多系统的数据,而应用程序就没有这些权限。访问权限检查目的,就是阻止恶意的程序通过驱动来达到某些不良动作。如果恶意程序能通过驱动读写系统内存,那么恶意程序就能访问到原来不能访问的数据。适当的访问权限检查,可以有效保护系统内存。



CE5中:

1、驱动使用MapCallerPtr()去检查指针参数和内嵌指针的权限。CE5内核检查指针参数是多余的,因为内核无法得知传递buffer的大小。所以内核只能检查buffer的1个byte。

2、访问权限检查可以通过调用进程的trust level,来开放或关闭权限。



CE6中:

1、  函数调用方式有所改变,参数已经可以包括指针参数的大小。所以内核现在能对指针参数,提供一个全面的访问权限检查。(后面会详细描述这些CE6的细节)

2、  驱动只需要检查内嵌指针,检查函数改为新的函数:CeOpenCallerBuffer().。此函数同时支持内存地址重建(marshalling)

3、  访问权限检查可以通过调用进程是内核态还是用户态,来开放或关闭权限。(将来可能修改为,通过权限等级来开关权限)



同步访问:当函数被调用时,在调用线程中完成访问内存的动作。

异步访问:如果一个驱动有线程,当函数调用完毕返回后,访问其他进程的内存,那就是“异步访问”。但需要注意的是,如果驱动有一个线程是在函数调用期间,访问其他进程的内存,在完成返回前,那也是“异步访问”。



指针映射和内存地址重建,是为了驱动能够访问调用者的buffer,而做的指针准备工作。驱动能在应用程序调用它们时,在其他进程空间中被调用运行。因为每个进程的虚拟地址空间,默认是与其他进程互斥、有冲突的。所以驱动必须在访问其他进程的空间前,做好准备工作。



CE5中,所有的进程共享一段公共的地址空间。为了能访问指针所指向的内存,驱动需要映射指针到自己所在的进程空间里。映射是对指针指向值的简单转换,只是让指针在公共地址空间中,指向其他进程slot。下面图片,为device.exe访问调用它的应用进程地址空间。





CE6中,每个进程都有自己唯一的地址空间,内存地址重建不能是简单的转换了。每块内存必须从一个进程复制到另一个进程中。或者在驱动进程中申请一个新的虚拟地址,并且映射为调用者正在使用的、原来的那块物理内存。另外,在驱动进程申请的资源,在驱动工作完毕后,必须释放掉。下图为,内核和udevice 创建的内存地址重建的示意图。







CE6的内存地址重建有许多方法的,包括buffer是in-only, in/out 或 out-only。基于这些设置,内存地址重建能确保适合时候的做读取的动作。这也能作为访问权限检查的功能,例如:用户态的应用程序不能传递共享的堆地址(对于应用是只读的),作为in/out 或out-only的参数。



为了解释驱动为何一定需要重建内存地址,分别从同步和异步访问的角度说明。

首先是同步:

1、  内核自动映射或者重组指针参数

2、  驱动必须注意内嵌指针。CE5中,驱动使用MapCallerPtr()来做检查,而CE6中改为CeOpenCallerBuffer()和CeFreeCallerBuffer()。

无论是MapCallerPtr还是CeOpenCallerBuffer函数,在使用buffer前都是一个很好的检查手段。



异步访问比较复杂:

CE5中,线程要访问调用线程的内存时,有些必须做些额外的工作。每个进程slot会防止其他进程访问它们,而每个线程有自己的权限去访问不同的进程。当调用线程切入到驱动后,带入了它自己本身的访问权限,是属于它所在的进程slot。因此访问调用线程内存的权限,在整个调用过程中都是有效的(这个比较特别)。而不同线程要访问其他进程slot,就必须先获得权限。



CE6和CE5类似,都需要做额外工作,但是做这些工作的原因就不一样了,也不容易解释。内核态和用户态的内存地址重建是不一样的,指针参数和内嵌参数也是不一样的。要保证驱动代码在所有模式下正常工作,就需要异步访问前,在其他线程中做好准备工作。



对于异步访问,指针变量和内嵌指针处理的方式是一样的。如果我们带入一个buffer是已经为同步访问映射或者重组过的,那么驱动访问这个buffer需要的工作是:

1、  CE5:驱动需要在异步线程中,调用SetProcPermissions()函数。以便在不同进程中访问同一个buffer。

2、  CE6:驱动需要调用CeAllocAsynchronousBuffer(),来申请一个可异步访问的buffer,给同步访问。这个动作必须在传递buffer给异步线程前调用。当线程结束对buffer的访问,调用CeFreeAsynchronousBuffer()来释放刚才申请的资源。



不幸的是,不是所有异步情况可以支持用户态驱动,用户态驱动不能异步回写指针参数。而内核态驱动、只读指针(不会回写到caller的)和内嵌指针就可以。简单来说,用户态驱动不支持异步访问。如果都按这个规则办,以后出现问题会少点。假如驱动一定需要异步访问caller buffer的话,在CE6中最好放在内核态中运行。(或者重新修改协议的框架,让caller内存的访问从不异步。又或者通知caller知道数据已经准备好,在驱动中通过回调方式获取数据)



驱动产品化的其他细节:

安全的从其他进程接受内存数据,安全复制和异常处理是必要的。

许多开发人员不太了解的一个安全风险问题:caller有一个buffer,而这个buffer被传递给驱动。当驱动还在使用此buffer 时,caller另一个线程可能有会同时使用这块buffer。恶意程序可以控制内嵌指针来获取内存的访问权限,或导致内存溢出,或者导致内存越界和异常。为防止这些情况,驱动必须把caller的buffer做备份,称为安全复制(secure copy),防止caller异步的修改。



第一个例子:caller传递内嵌指针给驱动,可以通过安全复制来防止攻击。驱动使用MapCallerPtr (CE5) 或CeOpenCallerBuffer (CE6)来检查指针的权限,同时映射和重组内存地址。如果驱动继续保存指针到caller buffer,caller会稍后的把指针指向其他内存,那么驱动可能会访问到错误的地址。驱动必须备份从caller传递下来的指针,防止异步的修改。同样,驱动必须备份从caller传递下来的buffer大小的内容。



因此,当需要调用MapCallerPtr 或CeOpenCallerBuffer。复制内嵌指针到本地空间,是一个明智的手段。不要保存映射、重组的指针到caller的buffer。不要未映射、重组前,使用caller的指针。谨慎处理buffer的大小长度,那么caller就不能通过指针越界访问了。



第二个安全复制例子是:文件名字。CreateFile函数会带入能有效访问的文件名。假如CreateFile先读取文件名、权限检查,通过检查后才使用这个文件名打开文件。CreateFile先传入了一个检查后的文件名,而异步修改为一个不能访问的文件名,那么会有一段时间内caller让 CreateFile进入打开非法文件。这可能只有很小的机会存在问题,但是黑客程序会利用这个缺点直到成功。这会影响整个系统的安全性。对这类攻击的保护方法是,在打开文件前,CreateFile一定要对文件名在caller不能访问的地方做备份。(CE6已经对CreateFile的文件名做了安全备份,这只是举例说明一下)



任何数据得到确认后,都需要做备份,防止在数据确认后有异步修改。安全备份可以简单复制buffer或指针到有效的栈,或者从一个临时的堆中申请地址空间。函数CeOpenCallerBuffer有一个强行备份参数,可以通过此参数做安全备份。可以选择使用 CeAllocDuplicateBuffer函数(这基于堆的分配,必要时候使用memcpy作为复制函数)。不管是否使用安全备份,你都需要保护从 caller传递进来的数据。



驱动使用异常处理,来作为简单的数据安全备份。重点注意的是: caller可能会传递一个指向无效的内存地址。因为应用程序可以传递一个指针指向一个没有分配的用户态地址,或者异步的释放内存。所以驱动必须经常使用try/except的语句来解决异常。还有,函数返回前,确认调用的申请内存被释放,离开所有的临界区。



进程间传递数据是比较复杂的,但是在驱动中,有些简单的规则可以依循:

情景
        

CE6驱动必须的动作
        



同步使用参数
        

如果需要安全备份,自己备份或者使用CeAllocDuplicateBuffer
        



异步使用参数
        

如果需要安全备份,自己备份或者使用 CeAllocDuplicateBuffer / CeFreeDuplicateBuffer.

否则使用CeAllocAsynchronousBuffer / CeFreeAsynchronousBuffer.
        



同步使用内嵌指针
        

必须使用CeOpenCallerBuffer / CeCloseCallerBuffer来地址重建和安全备份


        







异步使用内嵌指针
        

使用 CeOpenCallerBuffer 然后使用CeAllocAsynchronousBuffer.  必须在CeCloseCallerBuffer前调用 CeFreeAsynchronousBuffer。
        



记住,经常使用try/except。



CE6有些类可以帮助简化以上的操作,代码在public\common\oak\inc\marshal.hpp中。

    * MarshalledBuffer_t: 封装了CeOpenCallerBuffer、CeAllocAsynchronousBuffer和它们的释放函数。在所有内嵌指针中使用这个类。
    * DuplicatedBuffer_t: 封装了 CeAllocDuplicateBuffer 和释放函数. 需要安全备份时指针参数可以使用这个类。
    * AsynchronousBuffer_t: 封装了CeAllocAsynchronousBuffer和释放函数。需要异步访问指针参数可以使用这个类

C++的版本为:

情景
        

CE6驱动必须的动作

同步使用参数
        

如果需要安全备份,使用DuplicatedBuffer_t

异步使用参数
        

如果需要安全备份,使用DuplicatedBuffer_t

否则使用 AsynchronousBuffer_t

内嵌指针
        

使用 MarshalledBuffer_t

http://blogs.msdn.com/ce_base/ar ... -in-Windows-CE.aspx

使用特权

评论回复
地板
zyok| | 2009-11-3 13:37 | 只看该作者
路过,学习

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

99

主题

1078

帖子

0

粉丝