第十七章 内存映射文件
与虚拟内存相似,内存映射文件允许开发人员预订一块地址空间区域并给区域调拨物理存储器。不同之处在于,内存映射文件的物理存储器来自磁盘上已有的文件,而不是来自系统的页交换文件。
内存映射文件主要用于以下三种情况:
1)系统使用内存映射文件载入并运行.exe和动态链接库(DLL)文件。这大量节省了页交换文件的空间以及应用程序启动的时间。
2)开发人员可以用内存映射文件来访问磁盘上的数据文件。这使得我们可以避免直接对文件进行I/O操作和对文件内容进行缓存。
3)通过使用内存映射文件,我们可以在同一台机器的不同进程之间共享数据。Windows的确提供了其他一些方法来在进程间传送数据,但这些方法都是通过内存映射文件来实现的。因此,如果要在同一台机器的不同进程之间共享数据,内存映射文件是最高效的方法。
1 映射到内存的可执行文件和DLL
一个线程在调用CreateProcess的时候,系统会执行以下步骤:
1)系统会先确定CreateProcess所指定的可执行文件的位置,如果无法找到,系统将不会创建进程。
2)系统创建一个新的进程内核对象。
3)系统为新进程创建一个私有地址空间。
4)系统预订一块足够大的地址来容纳.exe.文件。待预订的地址空间区域的具体位置已经在.exe文件中指定。默认.exe文件的基地址为0x00400000。
5)系统会对地址空间区域进行标注,表明该区域的后备物理存储器来自磁盘上的.exe文件,而非来自系统的页交换文件。
当系统把.exe文件映射到进程的地址空间之后,会访问.exe文件中的一个段,这个段列出了一些DLL文件,它们包含了.exe.所需要调用的函数。然后系统会调用LoadLibrary来载入每个DLL,系统调用LoadLibarary来载入一个文件DLL的时候,与上面的4)和5)类似。
把所有的.exe文件和DLL文件都映射到进程的地址空间之后,系统会开始执行.exe文件的启动代码。当完成对.exe文件的映射后,系统会负责所有的换页(paging)、缓存(buffering)以及高速缓存(caching)操作。例如,如果.exe文件中的代码转到一个指令地址,但该地址沿未载入内存,那么会引发一个页面错误(page fault)。系统会检测到这个错误并自动将该页代码从文件映像载入到内存中。然后系统会把该内存映射到进程地址空间中的适当位置,并让线程继续执行,就好像该页代码早已载入内存一样。
如果一个应用程序已经在运行,那么当我们为这个应用程序创建一个新的进程时,系统只不过打开另一个内存映射视图(memory-mapped view),创建一个新的进程对象,并(为主线程)创建一个新的线程对象。这个新打开的内存映射视图隶属于一个文件映射对象(file-mapping object),这个对象用来标识可执行文件的映像。系统同时给进程对象和线程对象指定新的进程ID和线程ID。通过内存映射文件,同一个应用程序的多个实例可以共享内存中的代码和数据。也就是说,这里系统只不过把包含应用程序代码和数据的虚拟内存页面映射到第二个实例的地址空间中。
同一个可执行文件或DLL的多个实例不会共享静态数据,如果应用程序的一个实例修改了数据页面中的一些全局变量,系统会通过内存管理系统的写时复制(copy-on-write)特性来防止应用程序所有的实例的内存都会被修改。任何时候当应用程序试图写入内存映射文件的时候,系统会首先截获此类尝试,接着为应用程序试图写入的内存页面分配一块新的内存,然后复制页面内容,最后让应用程序写入到刚分配的内存块。最终的结果就是,应用程序的其他实例不会受到任何影响。
如果希望在同一个.exe文件或DLL的多个实例之间共享同一个变量,可以在编译的时候使用下面的编译器指示符在文件中来创建自己的段(section):
#pragma data_seg("sectionname")
举例来说,可以用下面的代码来创建一个名为“Shared”的段,它只包含一个LONG变量 :
#pragma data_seg("Shared")
LONG g_lInstanceCount = 0;
#pragma data_seg();
当编译器编译这段代码时,会创建一个名为Shared的段,并将pragma指示符之间所有带初始值(编译器只会将已初始化的变量保存在这个段中)的变量放到这个新的段中。变量后面的#pragma data_seg()告诉编译器停止将已初始化的变量放入Shared段中,而重新把它们放入默认段中。
另外,必须告诉链接器要共享这个段中的变量,可以使用链接器开头 /SECTION:Shared, RWS
或者直接把链接器开头嵌入到源代码中:
#pragma comment(linker, "/SECTION:Shared,RWS")
R表示READ,W表示WRITE,E表示EXCUTE,S表示SHARED