内存映射文件可以大大减少操作文件的开销,让程序运行更顺利,使不同进程共享数据变得更容易.
一.执行程序
当创建一个线程时,
1.系统只是保留了足够大的对应的.exe文件区域,将.exe文件本身作为物理内存,执行映射,却并未提交;
2.访问.exe文件的部分区域,以确定依赖的.dll(这将导致部分页面被提交);
3.依次LoadLirary(像为.exe保留区域一样,也为.dll保留区域,并有些类似第4步);
4.执行.exe的启动代码,并持续执行(执行到哪就提交哪块).
二.程序运行时修改全局变量或自修改代码
同一个程序可能运行多个实例,而每个进程都把原始文件当作物理内存看待,那么其中一个进程修改了某部分数据或代码,其他进程是否也被改动了呢?答案是否定的。由于Copy-On-Write的干预,每当一个进程试图修改全局变量(或代码)时,都会把这个变量所在的页重新拷贝一份,并把虚拟地址映射到这个拷贝上,此时怎么改都只是改动单独为此进程准备的副本,而不会影响其他进程。
三.同一程序多个实例的数据共享
是不是同一程序的多个实例就没办法共享数据了呢,有时这是很有用的!答案是可以共享,通过共享数据段可以很容易的在同一程序的多个实例之间共享数据。
其实.exe或.dll文件分为若干个节(section),例如.text节通常存放程序的代码,.data节存放已初始化的全局变量等等,每个节也都有自己的属性:READ、WRITE、EXECUTE、SHARED。我们可以自己创建一个节,并指定SHARED属性(当然通常还要有READ、WRITE属性,否则没有什么意义了),这样,编译程序时就会自动生成该节,包含在该节中的内容将被该程序的多个进程所共享。
示例代码如下:
unsigned int g_uRef = 0; // 必须是初始化的,否则放到.shared之外
#pragma data_seg()
#pragma comment(linker, "/SECTION:.shared,RWS")
__declspec(allocate(".shared")) int g_nforce; // 强制把 nforce 放入 .shared节中,无论它是否被初始化,很暴力哦
这样一个共享的节就创建好了,程序可以像使用普通的全局变量一个样使用它~
{
InterlockedExchangeAdd((PLONG) &g_uRef, 1);
/* do something */
InterlockedExchangeAdd((PLONG) &g_uRef, -1);
return 0;
}
四.使用内存映射文件
如果你需要想使用自己的变量一样来操作文件而不必事先把文件的全部或部分读取到特地分配的缓冲里,那么应该使用内存映射文件。
1.用CreateFile来打开指定的文件,如果打开成功将返回文件的句柄(HANDLE),否则返回INVALID_HANDLE_VALUE;
2.调用CreateFileMapping以创建文件映射对象,并传递上一步获得的HANDLE,将返回文件映射对象的句柄,失败的话返回NULL;
3.调用MapViewOfFile(Ex),并传递上一步获得的文件映射对象,当然还要指定从文件的多少字节偏移处开始映射,以及要映射的大小——从Start到Start+Size构成了一个View,当然可以传递Size为0,那么将一直映射到文件结尾,如果映射成功,将返回可操作的地址,就像一个变量一样,随便怎么弄;
4.使用这个地址,进行读写操作。通常不需要调用FlushViewOfFile来强制把修改写入到磁盘上的文件里,因为下一步的UnmapViewOfFile会把所有改动写入磁盘上的文件里的;
5.当一切都搞定后,可以调用UnmapViewOfFile来取消映射,这回释放所有保留区域。如果在MapViewOfFile时传递了FILE_MAP_COPY,那么此步骤将放弃所有的修改,原文件将一点都不变化,否则,将写入改动后的数据;
6.像关闭其他内核对象一样,调用CloseHandle依次关闭文件映射对象和文件对象,操作流程结束.
书中给出了示例操作流程:
HANDLE hFileMapping = CreateFileMapping(hFile, );
CloseHandle(hFile);
PVOID pvFile = MapViewOfFile(hFileMapping, );
CloseHandle(hFileMapping);
// Use the memory-mapped file.
UnmapViewOfFile(pvFile);
或许你想在不同的进程中简单的共享数据而不需要创建或打开什么磁盘上的文件,那么同样可以用内存映射文件——并且是最好的方法。此操作无需CreateFile了,直接调用CreateFileMapping并且给第一个参数传递INVALID_HANDLE_VALUE,并且传递一个独一无二的名字,其余的步骤类似,这样就建立了一个共享的数据。在另一个进程中,只需要同样的调用CreateFileMapping或OpenFileMapping,然后映射,那么就可以获得相同的共享数据,是不是很方便呢~
如果在调用CreateFileMapping时传递SEC_RESERVE,然后调用MapViewOfFile,那么获得的只是一个地址,却不能访问,因为还没提交。如果想使用,需要调用VirtualAlloc并传递MEM_COMMIT——这和操作由VirtualAlloc的MEM_RESERVE一样。