从win32中的写时复制(Copy on write )机制谈起

我们知道,内存映射文件的物理存储器来自磁盘上已有的文件,而不是来自也交换文件。系统在加载exe和dll文件的时候使用的是内存映射文件来载入并运行exe和dll,这样大大节省了页交换文件的空间以及应用程序的启动时间。所以,实际上系统载入exe文件的时候就是利用内存映射文件技术把磁盘中的exe文件映射到内存(实际上,系统只是预定了一块足够大的地址空间来容纳exe文件,一定要注意“预定”这个词,待预定的地址空间区域的具体位置已经在exe文件中指定。默认情况下,exe的基地址为0x00400000.系统也会对这个地址空间的区域进行标注,表明该区域的后备物理存储区来自磁盘上的exe文件,而非来自系统的页交换文件。

以上就是,系统装载可执行文件的时经常被称为“映射到地址空间”的解释了,实际上就是创建了exe文件对应的页交换文件,也就是系统为这个exe文件的执行预定了足够大的地址空间,在x86下为4G的地址空间。

以上是系统加载exe文件的过程,映射到地址空间之后,系统开始执行exe文件的启动代码。完成了这个映射过程后,系统会负责所有的换页(paging),缓存(buffering)以及高速缓存(caching)的操作。例如,如果.exe文件中的代码跳到一个指令地址,但是该地址尚未载入内存,那么会引发一个页错误(page fault)。系统会检测到这个错误并自动将该页代码从文件载入到内存。然后系统会把该内存页映射到进程地址空间中的适合位置,并让线程继续执行,就好像该页代码早就在内存中一样。


从理想来分析整个装载的过程,我们需要了解PE文件的结构,实际上,在载入的过程PE文件会按照磁盘中的各个页的排列顺序映射到地址空间中,也即是映射到内存映射文件中。改变的只是每一个页在磁盘中的对齐大小要按照在内存中的页对齐大小来排列了。假如,在磁盘中的文件对齐为0x1000,内存对齐其0x2000,那么PE文件的内存映像中每一也都将扩大1倍了,但是每一个页的相对位置不变。这样,对于磁盘映像到内存映像的双向都可以很容易的一一对应起来,这为windows虚拟内存的管理提供了很好的一个机制。文件对齐和内存对齐的值在PE文件中都有指定,具体的位置在PE的头部信息中。


介绍了背景知识,接着我们开始来谈copy on write这个机制。

假如一个应用程序已经在运行了,那么我们为这个应用程序创建一个新的对象时,系统只不过打开另一个内存映像视图(memory-mapped view ),创建一个新的进程对象,并为主线程创建一个新的线程对象。这个新打开的内存映像视图隶属于一个文件映射对象(file-mapping object),后者用来标示可执行文件的映像。系统同时给进程对象和线程对象分别指定新的进程ID和线程ID。,通过内存映射文件,同一个应用程序的多个实例可以共享内存中的代码和数据。

如果应用程序的一个实例修改了数据页面的一些全局变量,那么应用程序的所有实例的内存都将会被修改。由于这种类型的修改可能导致灾难性的后果,因此避免。

系统通过内存管理系统的写时复制特性来防止以上情况的发生。任何时候当应用程序视图写入内存映射文件的时候,系统会首先截获此类尝试,接着为应用程序试图写入的内存映射文件页面分配一块新的内存,然后复制页面的额内容,最后让应用程序写入到刚分配的内存块。最终的结果就是,应用程序的其他实力不会受到任何的影响。

从win32中的写时复制(Copy on write )机制谈起_第1张图片


下面我们来探讨一个关于内存映射文件的例子:

我们再使用ITA Hook的时候,需要获取一个exe文件的导入表,那么如何获取这个程序的导入表呢?

导入表是PE文件中的一个结构,可以根据PE的头部信息获取得到,因为导入表针对的dll以及函数的填充是在装载完成之后的,所以我们需要在程序被导入到内存中之后再获取。直接使用GetModuleHandle来获取模块的基地址。但是假如目标程序没有运行,那么我们需要使用内存映射文件相应的API将该程序导入到内存之后再获取导入表的信息。

相关的API有:

CreateFile,CreateFileMapping等操作。

关于内存映射文件的操作,以后再详细讨论。


你可能感兴趣的:(windows系统编程)