嵌入式设备与桌面PC的一个显著不同是它的应用程序中通常需要直接访问某一段物理内存,这在驱动程序中对物理内存的访问尤为重要,尤其是像ARM体系结构下,I/O端口也被映射成某一个物理内存地址。因此,与桌面版本Windows相比,Windows CE提供了相对简单的物理内存访问方式。无论是驱动程序还是应用程序都可以通过API访问某一段物理内存。
Windows CE的有些函数中需要用到物理内存结构体PHYSICAL_ADDRESS, Windows CE在ceddk.h中定义了PHYSICAL_ADDRESS,它其实是LARGE_INTEGER类型,其定义如下:
// in ceddk.h
typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS;
// in winnt.h
typedef union _LARGE_INTEGER{
 struct{
    DWORD LowPart;
    LONG HighPart;
 };
 LONGLONG QuadPart;
} LARGE_INTEGER;
可见,Windows CE中用64个Bit来代表物理地址,对于大多数32位的CPU而言,只需要把它的HighPart设置为0就可以了。
如果要直接访问某一个地址的物理内存,Windows CE提供了VirtualAlloc()和VirtualCopy()函数,VirtualAlloc负责在虚拟内存空间内保留一段虚拟内存,而VirtualCopy负责把一段物理内存和虚拟内存绑定,这样,最终对物理内存的访问还是通过虚拟地址进行。它们的声明如下:
// 申请虚拟内存
LPVOID VirtualAlloc(
 LPVOID lpAddress,     // 希望的虚拟内存起始地址
 DWORD dwSize,             // 以字节为单位的大小
 DWORD flAllocationType,  // 申请类型,分为Reserve和Commit
 DWORD flProtect           // 访问权限
);
// 把物理内存绑定到虚拟地址空间
BOOL VirtualCopy( 
 LPVOID lpvDest,           // 虚拟内存的目标地址
 LPVOID lpvSrc,            // 物理内存地址
 DWORD cbSize,             // 要绑定的大小
 DWORD fdwProtect          // 访问权限
);
VirtualAlloc对虚拟内存的申请分为两步,保留MEM_RESERVE和提交MEM_COMMIT。其中MEM_RESERVE只是在进程的虚拟地址空间内保留一段,并不分配实际的物理内存,因此保留的虚拟内存并不能被应用程序直接使用。MEM_COMMIT阶段才真正的为虚拟内存分配物理内存。
下面的代码显示了如何使用VirtualAlloc和VirtualCopy来访问物理内存。因为VirtualCopy负责把一段物理内存和虚拟内存绑定,所以VirtualAlloc的时候只需要对内存保留,没有必要提交。
FpDriverGlobals = 
(PDRIVER_GLOBALS) VirtualAlloc(
    0, 
    DRIVER_GLOBALS_PHYSICAL_MEMORY_SIZE, 
    MEM_RESERVE, 
    PAGE_NOACCESS);
 if (FpDriverGlobals == NULL) {
    ERRORMSG(DRIVER_ERROR_MSG, (TEXT(" VirtualAlloc failed!\r\n")));
    return;
 }
 else {
    if (!VirtualCopy(
    (PVOID)FpDriverGlobals, 
    (PVOID)(DRIVER_GLOBALS_PHYSICAL_MEMORY_START), 
    DRIVER_GLOBALS_PHYSICAL_MEMORY_SIZE, 
    (PAGE_READWRITE | PAGE_NOCACHE))) {
       ERRORMSG(DRIVER_ERROR_MSG, (TEXT("VirtualCopy failed!\r\n")));
       return;
    }
 }
CEDDK还提供了函数MmMapIoSpace用来把一段物理内存直接映射到虚拟内存。此函数的原形如下:
PVOID MmMapIoSpace( 
 PHYSICAL_ADDRESS PhysicalAddress, // 起始物理地址
 ULONG NumberOfBytes,                  // 要映射的字节数
 BOOLEAN CacheEnable                   // 是否缓存
);
其实,MmMapIoSpace函数内部也是调用VirtualAlloc和VirtualCopy函数来实现物理地址到虚拟地址的映射的。MmMapIoSpace函数的原代码是公开的,我们可以从%_WINCEROOT%\PUBLIC\COMMON\OAK\DRIVERS\CEDDK\DDK_MAP\ddk_map.c得到。从MmMapIoSpace的实现我们也可以看出VirtualAlloc和VirtualCopy的用法:
PVOID MmMapIoSpace (
    IN PHYSICAL_ADDRESS PhysicalAddress,
    IN ULONG NumberOfBytes,
    IN BOOLEAN CacheEnable
    )
{
PVOID pVirtualAddress; ULONGLONG SourcePhys; 
ULONG SourceSize; BOOL bSuccess;
 
    SourcePhys = PhysicalAddress.QuadPart & ~(PAGE_SIZE - 1);
    SourceSize = NumberOfBytes + (PhysicalAddress.LowPart & (PAGE_SIZE - 1));
 
    pVirtualAddress = VirtualAlloc(0, SourceSize, MEM_RESERVE, PAGE_NOACCESS);
    if (pVirtualAddress != NULL)
    {
        bSuccess = VirtualCopy(
            pVirtualAddress, (PVOID)(SourcePhys >> 8), SourceSize,
            PAGE_PHYSICAL | PAGE_READWRITE | (CacheEnable ? 0 : PAGE_NOCACHE));
 
        if (bSuccess) {
            (ULONG)pVirtualAddress += PhysicalAddress.LowPart & (PAGE_SIZE - 1);
        }
        else {
            VirtualFree(pVirtualAddress, 0, MEM_RELEASE);
            pVirtualAddress = NULL;
        }
    }
    return pVirtualAddress;
}
此外,Windows CE还供了AllocPhysMem函数和FreePhysMem函数,用来申请和释放一段连续的物理内存。函数可以保证申请的物理内存是连续的,如果函数成功,会返回虚拟内存的句柄和物理内存的起始地址。这对于DMA设备尤为有用。在这里就不详细介绍了,读者可以参考Windows CE的联机文档。