与虚拟内存相似,内存映射文件保留了一个地址空间区域,在需要时将它提交到物理存储器。它们的不同点是内存映射文件提交到物理存储器的数据来自磁盘上相应的文件,而不是系统页文件。
使用内存映射文件的目的有3个:
1)系统使用内存映射文件来加载和执行.EXE和.DLL文件。这极大地节省了系统页文件空间,也缩短了启动应用程序所需的时间;
2)使用内存映射文件访问磁盘上的数据。这既避免了对文件执行文件IO操作,也避免了为文件的内容申请缓冲区;
3)使用内存映射文件在多个进程间共享数据。Windows也提供了其他进程间通信的方法,但是这些方法都是使用内存映射文件实现的,所以内存映射文件时最高效的方式。
内存映射文件相关函数:CreateFileMapping、OpenFileMapping、MapViewOfFile、UnmapViewOfFile和FlushViewOfFile。
使用内存映射文件可以分为两步:第一步是使用CreateFileMapping创建一个内存映射文件内核对象,告诉操作系统内存映射文件需要的物理内存大小。这个步骤决定了内存映射文件的用途—即是为了磁盘上的文件建立内存映射还是为了多个进程共享数据建立共享内存。
CreateFileMapping函数可以创建或者打开一个命名的或未命名的映射文件对象:
HANDLE CreateFileMapping(
HANDLE hFile, //一个文件的句柄
LPSECURITY_ATTRIBUTES lpAttributes, //定义内存映射文件对象是否可以继承
DWORD flProtect, //该内存映射文件的保护类型
DWORD dwMaximumSizeHigh, //内存映射文件的长度
DWORD dwMinimumSizeLow,
LPCTSTR lpName //内存映射文件的名字
);
参数hFile指定要映射的文件的句柄,如果这是一个已经打开的文件的句柄(即CreateFile的返回值),那么将建立这个文件的内存映射文件;如果这个参数是-1,那么将建立共享内存;
参数flProtect指定内存映射文件的保护类型,取值是PAGE_REAFONLY或PAGE_READWRITE;
参数dwMaximumSizeHigh和dwMinimumSizeLow组合指定了一个64位的内存映射文件的长度。一种简单的方法是将这两个参数都设为0,那么内存映射文件的大小将与磁盘上的文件一致;
如果创建的是共享内存,其他进程不能再使用CreateFileMapping函数去创建同名的内存映射文件对象了,此时可以使用OpenFileMapping函数来打开创建好的对象:
HANDLE OpenFileMapping(
DWORD deDesiredAccess, //指定保护类型
BOOL bInheritHandle, //返回的句柄是否可被继承
LPCTSTR lpName //创建对象时使用的名字
);
参数dwDesiredAccess指定的保护类型有FILE_MAP_WRITE和FILE_MAP_READ;
使用内存映射文件的第二步是映射文件映射对象的全部或一部分到进程的地址空间中。可以认为该操作是为文件中的内容分配线性地址空间,并将线性地址和文件内容对应起来。所用的函数是MapViewOfFile:
LPVOID MapViewOfFile(
HANDLE hFileMappingObject, //前两个函数返回的内存映射文件对象的句柄
DWORD dwDesiredAccess, //指定保护类型:FILE_MAP_WRITE、FILE_MAP_READ
DWORD dwFileOffsetHigh, //从文件的哪个地址开始映射
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap //要映射的字节数,如果指定为0,则映射整个文件
);
当不使用内存映射文件时,通过UnmapViewOfFile函数撤销映射并使用 CloseHandle函数关闭内存映射文件的句柄:
BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);
如果修改了映射视图中的内存,系统会在试图撤销映射或文件映射对象被删除时自动将数据写到磁盘上,但程序也可以根据需要将视图中的数据立即写到磁盘上,使用函数FlushViewOfFile:
BOOL FlushViewOfFile(
LPCVOID lpBaseAddress, //开始的地址
SIZE_T dwNumberOfBytesToFlush //数据块的大小
);