Windows核心编程:内存映射文件

作者:shenzi

链接:http://blog.csdn.net/shenzi

Windows核心编程:内存映射文件
    与 虚拟内存相似,内存映射文件允许开发人员预定一块地址空间区域并给区域调拨物理存储器。不同之处在于内存映射文件的物理存储器来自磁盘上已有的文件,而不 是来自系统的页交换文件。一旦把文件映射到地址空间,我们就可以对它进行访问,就好像整个文件都已经在被载入内存一样。
     内存映射主要用于以下三种情况:
  • 系统使用内存映射文件来载入并运行.exe和动态链接库(DLL)。这大量节省了页交换文件的空间以及应用程序启动的时间。
  • 开发人员可以使用内存映射文件来访问磁盘上的数据文件。这使得我们可以避免直接对文件进行I/O操作和对文件内容进行缓存。
  • 通过使用内存映射文件,我们可以在同一台机器的不同进程间共享数据。Windows的确提供了其它一些方法来在进程间传送数据,但这些方法都是通过内存映射文件来实现的。因此,如果在同一台机器的不同进程之间共享数据,内存映射文件时最高效的方法。    
1.映射到内存的可执行文件和DLL
    当一个线程在调用CreateProcess的时候,系统会执行以下步骤:
  • 系统会先确定CreateProcess所指定的可执行文件所在的位置。如果无法找到该.exe文件,那么系统将不会创建进程,这时CreateProcess会返回FALSE.
  • 系统创建一个新的进程内核对象。
  • 系统为新进程创建一个私有地址空间。
  • 系统预定一块足够大的地址空间来容纳.exe。待预定的地址空间区域的具体位置已经在.exe文件中指定。 默认情况下,.exe文件的基地址是0x00400000。但是,只需在构建应用程序的.exe文件时使用/BASE连接器开关,我们就可以给自己的应用程序指定一个不同的地址。
  • 系统会对地址空间区域进行标注,表明该区域的后备物理存储器来自磁盘上的.exe文件,而并非来自系统的页交换文件。   

    当 系统把.exe文件映射到进程的地址空间之后,会访问.exe文件中一个段,这个段列出了一些DLL文件,它们包含该.exe文件调用到的函数。然后系统 会调用LoadLibrary来载入每个DLL,如果哪个DLL需要用到其它DLL,那么系统同样会调用其它DLL,那么系统同样会调用 LoadLibrary来载入相应的DLL。系统每次调用LoadLibrary来载入DLL的时候,执行的操作与刚才列出的后两步相似。

  • 系统会预定一块足够大的地址空间区域来容纳DLL文件。待预定的的地址空间区域的具体位置已经在DLL文件中指定。所有与Windows一起发布的系统DLL都有不同的基地址,这样即使把它们载入到同一个地址空间,也不会发生重叠。
  • 如 果系统无法在DLL文件指定的基地址处预定区域,这可能是因为该区域已经被另一个DLL或.exe占用,也可能是区域不够大,这时系统会尝试在另一个地址 来为DLL预定地址空间区域。如果DLL不包含重定位信息(当使用连接器的/FIXED开关来构建DLL),这意味着DLL必须被载入到指定的基地址,否 则无法被载入。如果对DLL执行重定位,重定位不仅需要占用页交换文件中额外的存储空间,而且会增加载入DLL所需的时间。
  • 系统会对地址空间区域进行标注,表明该区域的后备物理存储器来自磁盘上的DLL文件,而并非来自页交换文件。如果由于Windows不能将DLL载入到指定的基地址而必须重定位的话,那么系统还会另外进行标注,表明DLL中有一部分物理存储器映射到了页交换文件。   

    把所有的.exe文件和DLL文件都映射到进程的地址空间之后,系统会开始执行.exe文件的启动代码。当完成对.exe文件的映射后,系统会负责所有的换页(paging)、缓存(buffering)、以及高速缓存(caching)操作。
同一个可执行文件或DLL的多个实例不会共享静态数据
    如 果一个应用程序已经在运行,那么当我们为这个应用程序创建一个新的进程时,系统只不过是打开另一个内存映射试图(memory-mapped view),创建一个新的进程对象,并为主线程创建一个新的线程对象。这个新打开的内存映射视图隶属一个文件映射对象(file-mapping object),后者用来标识可执行文件的映像。系统同时给进程对象和线程对象分别制定新的进程ID和线程ID。通过使用内存映射文件,同一个应用程序的 多个实例可以共享内存中的代码和数据。
    下图是一个简单的视图,它描述了如何把应用程序中的代码和数据载入到虚拟内存,并将它们映射到地址空间中。

Image from book

    假设应用程序的第二个实例现在开始运行。这时系统只不过是把包含应用程序代码和数据的虚拟内存页面映射到第二个实例的地址空间中,如下图所示:
    
     如果一个应用程序的一个实例修改了数据页面中的一些全局变量,那么有用程序所有实例的内容都会被修改。由于这种类型的修改可能会导致灾难性的结构,因此必须避免。
     系统通过内存管理器的写时保护(copy-on-write)特性来防止这种情况的发生。任何时候当应用程序试图写入内存映射文件的时候,系统会首先截获 此类尝试,接着为应用程序试图写入的内存页面分配一块新的内存,然后复制页面内容,最后让应用程序写入到刚分配的内存块。下图描述了当应用程序的第一个实 例试图修改数据页面2中的一个全局变量时,会产生怎样的结果:

    系 统先分配了一页新的虚拟内存(New page),然后把数据页面2中的内容复制到新压面中。系统会更新第一个实例的地址空间,这样新的数据页面就会和原始数据页面一样,映射到进程地址空间中 的同一位置。现在系统不仅可以让进程修改全局变量的值,而且也不用担心会修改到同一个应用程序的其它实例的数据了。
    说 明:当系统创建一个进程时,会检查文件映像的所有页面。对那些通常需要用写时复制属性进行保护的页面,系统会立即从页交换文件中调拨存储器。但系统只是调 拨这些页面,而不会实际载入页面的内容。当程序访问到文件映像中的一个页面时,系统会载入相应的页面。如果该页从未修改过,那么可以舍弃其中的内容并在需 要时重新载入。但如果文件映像的该页面被修改过,那么系统必须把修改过的页面调换到页交换文件中。
在同一个可执行文件或DLL的多个实例间共享静态数据
    默认情况下,同一个.exe文件或DLL的多个实例之间不会共享全局或静态数据,这样的设计是最保险的。但是,有些情况下在同一个.exe文件或DLL的多个实例之间共享同一个变量不仅有用,而且方便。
     每个.exe文件或DLL文件映像由许多段组成。按照惯例,每个标准的段名称都以点号开始。

表1:可执行文件常用段
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值