WinCE6.0中应用程序如何直接访问物理空间

时间:2022-05-16 17:32:04
在实际开发过程中,经常希望能在应用程序中直接读写设备的物理空间。以前在做WinCE6.0下的MEMMgr时通过秘密加载一个内核态驱动实现了这个需求。但这种方式有一个明显的缺陷,每次读写都必须经由它才能完成。如果只是读取GPIO,那问题不算大。如果想通过这种方式实现视频播放的加速就比较困难了。估计非但不能加速,反而会变得更慢。

      早先曾与ZL仔细的讨论过这个问题,他当时在WinCE6.0上移植TCPMP,发现播放视频不太流畅,于是想通过直接写显存进行加速。目的很明确,在应用中申请一段虚拟空间,通过某种方法将其映射到显存上,视频解码过程中直接往映射过的虚拟空间上写。这种方法与使用GAPI有一点类似。

      实现这个需求,需要用到函数VirtualCopyEx()。看看帮助中关于它的说明,This function dynamically maps a virtual address to a physical address by creating a new page-table entry.This function is callable in kernel mode and in user mode, when the source and destination process handles are the active process.This function is similar to VirtualCopy, exceptVirtualCopyEx requires handles to the source and destination process.

      据此基本可以确定,我们的确可以在应用中申请一段虚拟空间,然后通过这个函数将其映射到某段物理空间上。其中目标进程是我们的应用,而源进程是NK.exe。为了实现在NK.exe中执行VirtualCopyEx(),可以加载一个内核态的驱动。更为方便的方法是移植一个OALIOCTL,并在IOControl()中添加一个case。这样,应用程序在做内存映射时就无需打开某个流驱动,直接调用KernelIoControl()即可。

      OALIOCTL中添加的关键代码如下。 

 1  typedef  struct  {
 2       void *     pvDestMem;
 3      DWORD    dwPhysAddr;
 4      DWORD    dwSize;
 5  } VIRTUAL_COPY_EX_DATA;
 6 
 7  #define  IOCTL_VIRTUAL_COPY_EX CTL_CODE (FILE_DEVICE_UNKNOWN,3333,METHOD_BUFFERED,FILE_ANY_ACCESS)
 8 
 9 
10  case  IOCTL_VIRTUAL_COPY_EX:
11  {
12      VIRTUAL_COPY_EX_DATA  * =  (VIRTUAL_COPY_EX_DATA * )pInBuf;
13      HANDLE hDst  =  (HANDLE)GetDirectCallerProcessId();
14      HANDLE hSrc  =  (HANDLE)GetCurrentProcessId();
15      fRet  =  VirtualCopyEx(hDst,p -> pvDestMem,hSrc,(LPVOID)p -> dwPhysAddr,p -> dwSize,
16      PAGE_READWRITE | PAGE_PHYSICAL | PAGE_NOCACHE);
17  } break ;

       应用程序中进行内存映射的关键代码如下。

 1  volatile  LPVOID GetVirtual(DWORD dwPhyBaseAddress, DWORD dwSize)
 2  {
 3       volatile  LPVOID pVirtual;
 4      VIRTUAL_COPY_EX_DATA vced;
 5      
 6       if (dwPhyBaseAddress & 0xFFF )
 7      {
 8           return  NULL;
 9      }
10      vced.dwPhysAddr  =  dwPhyBaseAddress >> 8 ;
11      pVirtual  =  VirtualAlloc( 0 ,dwSize,MEM_RESERVE,PAGE_NOACCESS);
12      vced.pvDestMem  =  pVirtual;
13      vced.dwSize  =  dwSize;
14      KernelIoControl(IOCTL_VIRTUAL_COPY_EX, & vced,  sizeof (vced), NULL, NULL, NULL);
15       return  pVirtual;
16  }
17 
18  //  WinCE6.0模拟器中应用程序直接写屏
19  PBYTE pLCDBuf  =  (PBYTE)GetVirtual( 0x33f00000 , 0x100000 );
20  memset(pLCDBuf, 0 , 0x100000 );
21 

       这种方法在WinCE6.0的模拟器中测试了一下,能达到预期的效果。

高手blog:http://www.cnblogs.com/we-hjb/archive/2010/02/25/1673815.html

liguosheng:

关键就是

BOOL VirtualCopyEx(
    HANDLE hDstProc,
    LPVOID lpvDest,
    HANDLE hSrcProc,
    LPVOID lpvSrc,
    DWORD cbSize,
    DWORD fdwProtect
);
fRet  =  VirtualCopyEx(hDst,p -> pvDestMem,hSrc,(LPVOID)p -> dwPhysAddr,p -> dwSize,
16      PAGE_READWRITE | PAGE_PHYSICAL | PAGE_NOCACHE);
hDstProc是调用进程的句柄,lpvDest是调用进程的虚拟空间,不是当前进程的虚拟空间,这一点要注意。该函数主要目的就是将应用程序(位于用户空间)的虚拟空间和内核空间的地址做映射,以便在用户空间能够访问物理地址。善于思考的人也许会问,为什么不能在应用程序当中调用VirtualCopyEx(),此函数callable in both kernel mode and user mode,也能实现例程当中的 0x33f00000到 0x100000的映射,原因是wince6.0应用程序不能访问物理地址,所以必须要在内核空间的应用程序才能访问了,所以需要在驱动(驱动默认处于内核态,除非使用在注册表中使用)中调用VirtualCopyEx().

Wince6.0当中VirtualCopy()只能在kernel mode下使用。

In Windows Embedded CE 6.0, Windows Embedded CE 6.0 R2, and Windows Embedded CE 6.0 R3, VirtualCopy is a kernel-mode-only function.

VirtualAlloc(),VirtualAllocEx(),VirtualCopyEx() can be used in both mode.


参考一下这篇博客有帮助:

wince6.0下的物理地址映射和共享
===========================================================
wince6.0 下,驱动都被放到内核空间下(用户模式驱动除外)。用户进程访问物理地址受到了限制,即用户进程再也不能通过 vitrualalloc virtualcopy 来完成物理地址的映射。所以用户进程访问物理地址控制外围寄存器只能通过内核驱动来完成。大体的思路如下:

wince6.0提供了两个升级版的内存分配和映射函数,VirtualAllocExVirtualCopyEx,就是在原版的基础之上增加了一个进程句柄函数,所以可以在内核驱动中先获取调用者进程的句柄,再用上述的两个Ex函数,为该进程保留空间和映射物理地址。保留好的空间地址,在内核驱动中和用户进程中都是有效的。(在驱动中有效是因为用户进程是该驱动的调用者进程)。完成映射之后,就可以在用户进程中直接改变物理地址的值了。

下面介绍下这个两个函数的使用。

被映射的物理地址应该向上取整,保证为页面的起始处。所以被映射的大小也要做相应的增加。sDevPhysAddr是被映射的起始物理地址。dwSize是映射的大小。

SourcePhys = sDevPhysAddr & ~(PAGE_SIZE - 1);

SourceSize = dwSize + (sDevPhysAddr & (PAGE_SIZE - 1));

lpUserAddr=VirtualAllocEx(hDesProcess, 0, SourceSize, MEM_RESERVE,PAGE_NOACCESS);其它的参数,除了进程句柄外与vitrualalloc virtualcopy都是相同的。对于VirtualCopyEx来说

用户进程的句柄是目标进程句柄,驱动所在的进程句柄是源进程句柄。

由于物理地址向上取整,所以映射完成后的虚拟地址应该做出调整:

lpUserAddr=(LPVOID)((ULONG)lpUserAddr+(sDevPhysAddr & (PAGE_SIZE - 1)));

gUserAddr = lpUserAddr;

返回真正所需的地址。

当使用完后,释放资源如下:

VirtualFreeEx(pvProcess,(PVOID)((ULONG)gUserAddr&~(ULONG)(PAGE_SIZE-1)),0,MEM_RELEASE);

完成上述内容,首先是做个简单的流模式的内核驱动(见资源中心的FirstSubDll.rar),笔者在应用程序,用户模式驱动,内核驱动三个环境下VirtualAllocExVitualCopyEx映射物理地址做过测试。只有内核驱动能完成,其他的都是不支持的请求。Wince6.0限制的应用程序对物理地址的访问,提高系统的安全性,只有内核驱动(被系统信赖的)才能访问物理地址及外围设备寄存器。在驱动中给出IOCTL接口,通过DeviceIoControl完成物理地址映射和资源释放。应用加载驱动调用接口,获取完成映射的虚拟地址,直接读写,可以改变RAM值或某个外围设备寄存器的值(如通过GPIO控制某个设备的电源开关)。

前面提到过的保留好的空间地址,在内核驱动中和用户进程中都是有效的。通过此点可以让内核驱动和应用程序共享一段物理RAM。内核进程和用户进程内存共享与拷贝内核驱动中的数据块到用户已分配的存储空间中相比,可以节省内存资源和提高效率。特别是在摄影设备中,需要大量的存储,若不再应用和驱动*享内存,就会损失大量的RAM资源。