Windows核心编程(八)

第十七章 内存映射文件

与虚拟内存相似,内存映射文件允许开发人员预订一块地址空间区域并给区域调拨物理存储器。不同之处在于,内存映射文件的物理存储器来自磁盘上已有的文件,而不是来自系统的页交换文件。

内存映射文件主要用于以下三种情况:

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


2 映射到内存的数据文件 

Windows操作系统使我们能够把数据文件映射到进程的地址空间中,这样一来,对大型数据流进行操控就非常容易。
要作用内存映射文件,需要执行下面三个步骤:
(1)创建或打开一个文件内核对象,该对象标识了我们想要用作内存映射文件的那个磁盘文件。通过调用CreateFile函数。
(2)创建一个文件映射内核对象(file-mapping kernel object)来告诉系统文件的大小以及我们打算如何访问文件。CreateFileMapping函数。
(3)告诉系统把文件映射对象的部分或全部映射到进程的地址空间中。MapViewOfFile函数。
用完内存映射文件之后,必须执行下面三个步骤来做清理工作:
(1)告诉系统从进程地址空间中取消对文件映射内核对象的映射。
(2)关闭文件映射内核对象。
(3)关闭文件内核对象。
Windows允许我们以同一个数据文件为后备存储器来创建多个文件映射对象。Windows并不保证这些不同的文件映射对象的各个视图是一致的。系统只保证在同一文件映射对象的多个视图间保持一致。

3 用内存映射文件在进程间共享数据

内存映射文件的数据共享机制是通过让两个或多个进程映射同一个文件映射对象的视图来实现的,它意味着进程间共享相同的物理存储页面。因此,当一个进程在文件映射对象的视图中写入数据的时候,其他进程会在它们的视图中立即看到变化。注意,对多个进程共享同一个文件映射对象来说,所有进程使用的文件映射对象的名称必须完全相同。
以启动应用程序为例:当一个应用程序启动时,系统会先调用CreateFile来打开磁盘上的.exe文件。接着系统会调用CreateFileMapping来创建文件映射对象。最后系统会以新创建的进程的名义调用MapViewOfFileEx(并传入SEC_IMAGE标志),这样就把.exe文件映射到了进程的地址空间中,这里之所以用MapViewOfFileEx而不是MapViewOfFile,是为了把文件映射到指定的基地址(保存在.exe的PE文件头中)。系统然后创建进程的主线程,在映射得到的视图中取得可执行代码的第一个字节的地址,把该地址放到线程的指令指针中,然后让CPU开始执行其中的代码。
如果用户启动同一个应用程序的第二个实例,那么系统会发现该.exe文件已经有一个文件映射对象,因此就不会再创建一个新的文件对象或文件映射对象,而是会在新创建的进程的地址空间中再次映射.exe文件的一个视图。至此,系统已经把同一个文件同时映射到两个地址空间中。

如果为了共享数据而必须让应用程序在磁盘上创建数据文件并把数据保存在文件中,那将非常不方便。事实上,系统能够创建以页交换文件为后备存储器的内存映射文件,这样就不需要用磁盘上专门的文件来作为后备存储器了。这样方法很简单,它不必打开或创建一个专门的磁盘文件,所以不用CreateFile。只需要像原来那样调用CreateFileMapping,并将INVALID_HANDLE_VALUE作为hFile参数会话。这告诉系统我们创建的文件映射对象的物理存储器不是磁盘上的文件,而是希望系统从页交换文件中调拨物理存储器。所需分配的存储器大小由CreateFileMapping的dwMaximumSizeHigh和dwMaximumSizeLow参数决定。

一旦创建了内存映射文件,并把一个视图映射到了进程的地址空间中,就可以像使用任何内存区域一样使用它了。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值