每个进程都有独立的4GB逻辑地址空间,32位的Windows系统允许每一个进程独立访问自己的内存,即独立于其它进程,也即它自己的32位逻辑地址空间。操作系统将把每一个进程的逻辑地址转换成实际的物理地址,独立的地址空间可以使其他已经出错的进程之间相互隔离,入阁一个进程通过他自己的内存空间处理数据,其他的进程就比在DOS中安全,在DOS中的所有应用程序共享相同的物理内存空间,虽然这带来了许多好处,但在不同进程之间转的指针,就会出现一些麻烦。在一个进程中,一个给定的逻辑地址将与另一进程的指针不会有相同的逻辑地址。
那么怎么样才能在32位的Windows系统中达到共享内存的目的呢?
随着进程的分离内存空间的出现,进程不能简单地使用GlobalAlloc()函数来分配内存,并把它转递给另一个进程来共享,一个进程检查有另一个进程分配的指针,他只是指向随机地址。然而,在32位的Windows系统支持在进程间共享内存映象文件,可以通过内存映象文件来达到内存共享的目的。
32位的Windows系统的虚拟内存系统具有把实际内存映象到页面文件或交换文件的能力.通过把内存映象到任何想映象的文件,包括系统页面本身,应用程序就可以拓展这种能力。文件影响能用来提供更快更简捷的文件访问方式,并提供内存共享。
把文件映像到内存,首先必须调用CreateFileMapping()函数,然后再调用MapViewOfFile函数,把文件视映像到进程地址空间上。
C:专家点评
要把文件映像到内存,首先必须调用CreateFileMapping()函数,它需要用一个由CreateFile()函数打开并返回的文件句柄,对大多数共享内存应用程序。必须把此句柄设置为0xFFFFFFFF,用来指定系统页面文件。通过使用上面的特殊句柄,可以不调用CreateFile函数,当然在完成时,也不必有一个内存的磁盘文件拷贝。
CreateFileMapping()函数的第二个参数是一个指向SECURITY_ATTRIBUTES结构的指针,它指明返回的句柄是否可以被子进程所继承。另外,在SECURITY_ATTRIBUTES结构中,也包括一个安全性描述的子指针,它由WinNT支持它的安全机制。
第三个参数允许指定内存块的访问权限,权限值有PAGE_READONLY、PAGE_READWRITE和PAGE_WRITECOPY,PAGE_WRITECOPY可以在指定页面上拷贝并写访问。这意味着当一个进程映像此内存并写入时,它将得到自己修改数据的拷贝,面不是写到共享内存空间,另外,可以把几个标志一起使用来指定其他部分的属性。
其他参数允许指定内存块的最大尺寸,如果内存块的尺寸比第一个 参数中指定的文件尺寸还要大,这个文件就增大。
最后一个参数为内存映像对象指定名字,通过调用CreateFileMapping函数和OpenFileMapping函数,其他进程可用这个名字来访问相同的文件映像。
一旦一个内存映像对象由CreateFileMapping()创建成功,可以调用MapViewOfFile函数把文件视映像到进程地址空间上,这个函数需要用一个由CreateFileMapping()函数或OpenFileMapping()函数返回的句柄,并允许指定访问模式和映像的字节数,以及文件映像对象中的偏移量。另外,还可以使用MapViewOfFileEx()函数来实现同样的功能,只是此函数还允许指定映像对象的起始地址。
当用完映像文件后,可以通过调用UnmapViewOfFile函数,释放映像内存并把一些映像数据写入文件(除非它是交换文件),如果想立即把数据写回磁盘文件,那就需要调用FlushViewOfFile()函数把映像内存写入文件。
为了便于读者对以上程序的理解和进行下一步的学习,这里再简单介绍一下Win32内存模式。在开始学习Win32内存管理之前,最好先了解一些16位Windows与32位Windows的区别,它们之间的主要区别是把寄存器和指针长度变为32位。
Win32 API允许应用程序访问虚拟内存,如果想保存大量的数据,这十分有用,尽管大部分空间从来没有用于物理存储。虚拟内存页有四种状态,提交页面被分配一个物理存储单元,不管它是在是在交换文件中,提交页面被锁定后就强行留在内存中,直到解锁为止。保留页面存储一块保留地址国,它不被分配任何存储单元,自由页面都没有用。
VirtualAlloc()函数可用来分配从指定逻辑内存开始的一定范围内的地址,该函数的另一个参数允许指定访问保护标志,并指定内存是否被保存或被用于物理存储单元,也可以调用此函数来调配前面保存的虚拟内存页。
可以通过调用GloballLock()函数来强制使内存块留在物理内存中,应该谨慎使用此函数,因为它阻止系统管理这块内存,直到调用GlobalUnlock()函数为止。如果一个进程锁定了一大块内存,其他进程就会终止,不管还有多少内存。
函数VirtualProtect()用于改变一块内存的访问权限,VirtualQuery()函数返回内存页的住处。如果想访问其他进程的页面,可以通过调用VirtualProtecteEx和VirutalQueryEx两个函数来实现。
D:测试创建和打开文件映射的时候老是得到"句柄无效"的错误, 仔细看了MSDN以后才发觉是函数认识不透, 这里把相关的解释翻译出来
HANDLE CreateFileMapping(
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpAttributes, //安全设置
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCTSTR lpName
);
1) 物理文件句柄
2) 保护设置
3) 高位文件大小
4) 低位文件大小
5) 共享内存名称
7) 调用CreateFileMapping的时候GetLastError的对应错误
8) 相关服务或者平台的命名保留
内存映射API函数CreateFileMapping创建一个有名的共享内存:
HANDLE CreateFileMapping(
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCTSTR lpName
);
与虚拟内存类似,保护方式可以是PAGE_READONLY或是PAGE_READWRITE。如果多进程都对同一共享内存进行写访问,则必须保持相互间同步。映射文件还可以指定PAGE_WRITECOPY标志,可以保证其原始数据不会遭到破坏,同时允许其他进程在必要时自由的操作数据的拷贝。
在创建文件映射对象后使用可以调用MapViewOfFile函数映射到本进程的地址空间内。
下面说明创建一个名为MySharedMem的长度为4096字节的有名映射文件:
HANDLE hMySharedMapFile=CreateFileMapping((HANDLE)0xFFFFFFFF),
NULL,PAGE_READWRITE,0,0x1000,"MySharedMem");
并映射缓存区视图:
LPSTR pszMySharedMapView=(LPSTR)MapViewOfFile(hMySharedMapFile,
FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);
其他进程访问共享对象,需要获得对象名并调用OpenFileMapping函数。
HANDLE hMySharedMapFile=OpenFileMapping(FILE_MAP_WRITE,
FALSE,"MySharedMem");
一旦其他进程获得映射对象的句柄,可以象创建进程那样调用MapViewOfFile函数来映射对象视图。用户可以使用该对象视图来进行数据读写操作,以达到数据通讯的目的。
当用户进程结束使用共享内存后,调用UnmapViewOfFile函数以取消其地址空间内的视图:
if (!UnmapViewOfFile(pszMySharedMapView))
{
}