一.内核对象和地址空间
为了更好地理解本文后面的内容,在介绍内存映像文件之前我们先简单回顾一下Window
s中内核对象和地址空间的有关概念。在Windows中有各种内核对象,如事件、文件、进
程、旗语、互斥体等。内核对象是由系统内核分配管理的一段内存块,只有系统内核能
够直接访问这一段内核对象数据,而应用程序只能通过Windows提供的一系列函数按规定
的方式去操作这些内核对象。我们通过调用有关Windows API函数创建内核对象(这些函
数通常带有Create前缀)。当我们调用一个函数创建内核对象的时候,该函数通常返回
一个标识该对象的句柄(HANDLE)。我们把标识该内核对象的句柄传递给有关windows函数
,告诉系统去操作哪一个内核对象。内核对象属于系统内核而不是进程,内核对象可被
多个进程访问使用,也即可在多个进程间共享。每个内核对象自身有一个引用计数值,
它记录有多少进程使用该对象,当引用计数值为零时由系统负责销毁内核对象。在多个
进程间共享内核对象有三种方法:句柄继承、命名、句柄复制。在本文后面将要提到的
文件映像对象亦是内核对象。
在Windows中每一个进程都有自己私有的地址空间,对32位的进程来说地址空间的大小是
从0x00000000 到 0xFFFFFFFF共4GB范围。这个空间仅仅是内存地址的范围,而不是计算
机的物理存储RAM空间,因而又称为虚拟内存空间。使用某段虚拟内存空间需要经过保留
和提交两个过程,将物理存储分配映像到地址空间的该段区域上。物理存储不应理解为
RAM,实际上应理解为来自于磁盘上(通常为硬盘)的系统内存分页文件,在必要时(读
写访问时)其内容才会被系统写到RAM中或从RAM中写回来。磁盘上的这种系统内存分页
文件又称为虚拟内存。
二. 内存映像文件
几乎每个应用程序都要和文件打交道。比如,应用程序有时需要打开、读取、再关闭文
件;而有时需要打开文件,将数据读到一个缓冲区再写回到文件中另一个位置。通常实
现起来都显得有点繁琐。Microsoft Windows提供了满足这两方面要求的最佳解决途径:
内存映像文件。
象使用虚拟内存一样,使用内存映像文件同样需要经过保留和提交两个过程,首先在进
程内存空间保留一块区域,然后提交物理存储给这段区域。不同的是物理存储来自于磁
盘上的文件,而不是系统的分页文件。也即将磁盘上指定的数据文件作为虚拟内存,这
个实现过程被称为文件映像,可以将文件全部或部分映像到进程的地址空间中。文件映
像过以后,可以把文件映像的部分当作已全部被载入内存一样的去访问它,这时又称它
为内存映像文件。
内存映像文件通常有三个方面的应用:
1. 系统使用内存映像文件载入和执行.EXE和.DLL文件。一方面节省了系统分页文件空
间,另一方面缩短了加载应用程序开始执行所需的时间。
2. 使用内存映像文件访问磁盘上的数据文件。绕开对文件实行I/O操作和对文件内容的
缓冲,交由操作系统内核去完成。
3. 使用内存映像文件可以实现在多个进程间彼此共享数据。Windows提供了在进程间进
行数据通信的其它多种方法。但这些方法也是通过内存映像文件来实现的,所以内存映
像文件是实现进程间通信最有效率的方法。
三. 内存映像文件使用步骤
要使用内存映像文件,可以按以下步骤:
(1) 调用Windows API 函数CreateFile()创建或是打开一个文件,得到一个标识该文
件(内核对象)的句柄,它确定了哪一个磁盘文件将要作为内存映像文件。
(2) 将(1)中得到的文件对象句柄作为第一个参数调用Windows API 函数CreateFileM
apping()创建一个文件映像对象。通知系统该文件的大小及对该文件的访问方式,同
时也得到一个标识该文件映像对象的句柄。
(3) 将(2)中得到的文件映像对象句柄作为第一个参数调用Windows API 函数MapView
OfFile()通知系统映像文件全部或部分内容到进程的某一段地址空间,并将此段空间
首地址通过该函数返回。这一步也就是使用虚拟内存的保留和提交过程,此后就可以利
用此首地址实现对文件内容的读写了。
也可以不做第一步,直接从第二步开始,但这时要用INVALID_HANDLE_VALUE为参数作为
标识文件对象的句柄,这时系统以其分页文件作为内存映像文件而不用指定磁盘上的哪
一个磁盘文件。当你使用完内存映像文件后,需要做以下几步清除动作。
(1) 调用Windows API 函数UnmapViewOfFile(),通知系统释放文件映像对象在进程地
址空间中占用的区域。
(2) 调用Windows API 函数CloseHandle(),分别关闭文件映像对象和文件对象。
四. 内存映像文件应用举例
1.应用一 —— 系统加载.EXE和.DLL
当一个.EXE文件被载入准备运行时,系统要执行以下几步:
(1) 系统定位.EXE文件在磁盘的位置。
(2) 系统创建一个新的内核对象——进程。
(3) 系统为此进程创建其私有地址空间。
(4) 系统保留地址空间中足够大的一块区域容纳.EXE文件。而这块区域的基地址在.EXE
文件中有所指定,通常在0x00400000处。
(5) 系统记下支持这块区域的物理存储是.EXE文件,而不是在系统内存分页文件。
这之后系统访问.EXE文件有关信息项,确定所要载入哪些.DLL(动态连接库)文件。系
统调用LoadLibrary函数载入.DLL文件,执行类似上述(4)、(5)步的操作。
(1) 系统保留地址空间中足够大的一块区域容纳.DLL文件。同样其基地址在.DLL件中有
所指定。 Visual C++下制作DLL,默认情况下是 0x10000000处。
(2) 如果从该基地址开始的区域已被另一个.DLL或.EXE所占,或从该基地址开始的区域
不够大,系统将在地址空间寻找另一块区域,保留给.DLL文件。
(3) 系统记下支持这块区域的物理存储是在.DLL文件,而不是在系统内存分页文件。
在所有的.EXE和.DLL文件被映像到进程的地址空间中后,系统开始执行.EXE文件的入口
点代码。
2.应用二 —— 进程间共享数据
Windows的确提供了很多快速、方便的机制用于应用程序间共享数据和信息。这些机制包
括RPC、COM、OLE、DDE、WINDOWS消息(特别是WM_COPYDATA),剪贴板,管道,套节字
等等。然而在Windows中,同一台机器上多进程间共享数据最底层的机制是内存映像文件
。也就是说在同一台机器上,采用上述提到的方法实现进程间通信,最终全都是通过内
存映像文件分别实现各自特定的任务。因此使用内存映像文件是提高这类程序性能的一
种极好途径。
多进程间共享数据是通过将同一个文件映像对象的同一块区域映像到各进程地址空间中
来实现的。映像到在各个进程中的地址空间区域可能有所不同,但是各进程的这段区域
共享相同的物理存储分页文件,如图1所示。因此,当其中一个进程向文件映像对象的这
一区域写入数据时,其他进程在它们自身的地址空间中就能看到这一区域的变化。
让我们接着看系统加载.EXE启动一个应用程序这个例子。当启动一个程序时,系统调用
CreateFile函数打开磁盘上的.EXE文件。然后系统调用CreateFileMapping函数创建一个
文件映像对象,最后调用MapViewOfFileEx (用SEC_IMAGE 标志)将.EXE文件映像到进程
的地址空间中。此处用MapViewOfFileEx代替MapViewOfFile使文件映像到进程地址空间
中的首地址是存储在.EXE文件中指定的位置。然后系统创建进程的主线程,把此映像区
域可执行代码区的第一个字节的地址赋给线程的执行指针,然后让CPU开始执行代码。
如果用户执行同一个程序第二个实例时,系统会发现所要创建.EXE文件的文件映像对象
在内核中已经存在,因而不会再创建新的文件对象或文件映像对象。取而代之的是,系
统再次映像文件,这次是在新创建进程的地址空间中。可见系统同时映像同一个文件到
两个地址空间中。显然这更有效使用了内存,因为两个进程共享同一个包含正在执行代
码部分的物理存储文件。
象所有共享内核对象方法一样,可以使用句柄继承、命名、句柄复制三种方法在多个进
程间共享文件映像对象。下面列出了用命名方法共享内核对象,实现在两个进程间共享
数据的部分具体代码。用命名方法实现多个进程共享同一个文件映像对象时,所有进程
只需要对共享的文件映像对象使用完全相同的名称。
进程1:
//创建一个物理存储来自系统分页文件的文件映像对象,
//其大小为4 KB 命名为MMFSharedData.
s_hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0, 4 * 1024, TEXT("MMFSharedData"));
//将该对象内容全部映像到进程地址空间中
PVOID pView = MapViewOfFile(s_hFileMap,
FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
进程2:
// 打开名称为MMFSharedData 的文件映像对象
HANDLE hFileMapT = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE,
FALSE, TEXT("MMFSharedData"));
//将该对象内容全部映像到进程地址空间中
PVOID pView = MapViewOfFile(hFileMapT,
FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
这两个进程在其各自pView指针开始处4KB内容的物理存储来自同一个分页文件的同一段
区域,因而具有相同的内容,也即它们共享同一段数据。