内存映射文件

和虚拟内存一样,内存映射文件可以用来保留一个进程地址区域;但是,与虚拟内存不同,它提交的不是物理内存或是虚拟页文件,而是硬盘上的文件。将文件映射成内存,我们可以像使用内存一样使用文件.

  使用场合

它有三个主要用途:

系统加载EXE和DLL文件

操作系统就是用它来加载exe和dll文件建立进程,运行exe。这样可以节省页文件和启动时间。

访问大数据文件

如果文件太大,比如超过了进程用户区2G,用fopen是不能对文件进行操作的。这时,可用内存映射文件。对于大数据文件可以不必对文件执行I/O操作,不必对所有文件内容进行缓存。

进程共享机制

内存映射文件是多个进程共享数据的一种较高性能的有效方式,它也是操作系统进程通信机制的底层实现方法。RPC、COM、OLE、DDE、窗口消息、剪贴板、管道、Socket等都是使用内存映射文件实现的。


相关函数介绍



CreateFile(文件名,访问属性,共享模式,…) 

作用:创建文件内核对象API

其中,访问属性有:

0 不能读写 (用它可以访问文件属性)

GENERIC_READ

GENERIC_WRITE

GENERIC_READ|GENERIC_WRITE;

共享模式:

0 独享文件,其他应用程序无法打开

FILE_SHARE_WRITE

FILE_SHARE_READ|FILE_SHARE_WRITE

这个属性依赖于访问属性,必须和访问属性不冲突。

当创建失败时,返回INVALID_HANDLE_VALUE。


HANDLE CreateFileMapping(Handle 文件,PSECURITY_ATTRIBUTES 安全属性,DWORD 保护属性,DWORD 文件大小高32位,DWORD 文件大小低32位,PCTSTR  映射名称)

作用:创建文件映射内核对象

“文件”是上面创建的句柄;

“安全属性”是内核对象需要的,NULL表示使用系统默认的安全属性;“保护属性”是当将存储器提交给进程空间时,需要的页面属性:PAGE_READONLY, PAGE_READWRITE和PAGE_WRITECOPY。这个属性不能和文件对象的访问属性冲突。除了这三个外,还有两个属性可以和它们连接使用(|)。当更新文件内容时,不提供缓存,直接写入文件,可用SEC_NOCACHE;当文件是可执行文件时,系统会根据节赋予不同的页面属性,可用SEC_IMAGE。另外,SEC_RESERVE和SEC_COMMIT用于稀疏提交的文件映射,详细介绍请参考下文。

“文件大小高32位”和“文件大小低32位”联合起来告诉系统,这个映射所能支持的文件大小(操作系统支持264B文件大小);当这个值大于实际的文件大小时,系统会扩大文件到这个值,因为系统需要保证进程空间能完全被映射。值为0默认为文件的大小,这时候如果文件大小为0,创建失败。

“映射名称”是给用户标识此内核对象,供各进程共享,如果为NULL,则不能共享。

对象创建失败时返回NULL。

创建成功后,系统仍未为文件保留进程空间。


PVOID MAPViewOfFile(HANDLE 映射对象,DWORD访问属性,DWORD 偏移量高32位,DWORD 偏移量低32位,SIZE_T 字节数)



作用:文件映射内核对象映射到进程空间



“映射对象”是前面建立的对象;

“访问属性”可以是下面的值:FILE_MAP_WRITE(读和写)、FILE_MAP_READ、FILE_MAP_ALL_ACCESS(读和写)、FILE_MAP_COPY。当使用FILE_MAP_COPY时,系统分配虚拟页文件,当有写操作时,系统会拷贝数据到这些页面,并赋予PAGE_READWRITE属性。

可以看到,每一步都需要设置这类属性,是为了可以多点控制,试想,如果在这一步想有多种不同的属性操作文件的不同部分,就比较有用。

“偏移高32位”和“偏移低32位”联合起来标识映射的开始字节(地址是分配粒度的倍数);

“字节数”指映射的字节数,默认0为到文件尾。

 

当你需要指定映射到哪里时,你可以使用:

PVOID MAPViewOfFile(HANDLE 映射对象,DWORD访问属性,DWORD 偏移量高32位,DWORD 偏移量低32位,SIZE_T 字节数,PVOID 基地址)

“基地址”是映射到进程空间的首地址,必须是分配粒度的倍数。


BOOL FlushViewOfFile(PVOID 进程空间地址,SIZE_T 字节数)



作用:保存文件修改, 为了提高速度,更改文件时可能只更改到了系统缓存,这时,需要强制保存更改到硬盘,特别是撤销映射前。



“进程空间地址”指的是需要更改的第一个字节地址,系统会变成页面的地址;

“字节数”,系统会变成页面大小的倍数。

写入磁盘后,函数返回,对于网络硬盘,如果希望写入网络硬盘后才返回的话,需要将FILE_FLAG_WRITE_THROUGH参数传给CreateFile。

 

当使用FILE_MAP_COPY建立映射时,由于对数据的更改只是对虚拟页文件的修改而不是硬盘文件的修改,当撤销映射时,会丢失所做的修改。如果要保存,怎么办?

你可以用FILE_MAP_WRITE建立另外一个映射,它映射到进程的另外一段空间;扫描第一个映射的PAGE_READWRITE页面(因为属性被更改),如果页面改变,用MoveMemory或其他拷贝函数将页面内容拷贝到第二次映射的空间里,然后再调用FlushViewOfFile。当然,你要记录哪个页面被更改。



BOOL  UnmapViewOfFile(PVOID pvBaseAddress)



作用:撤销映射, pvBaseAddress  这个地址必须与MapViewOfFile返回值相同。



CloseHandle(HANDLE)



作用:关闭内核对象



在不需要内核对象时,尽早将其释放,防止内存泄露。由于它们是内核对象,调用CloseHandle(HANDLE)就可以了。

在CreateFileMapping后马上关闭文件句柄;

在MapViewOfFile后马上关闭内存映射句柄;

最后再撤销映射。





内存映射文件的使用步骤


    2.1 创建或打开一个文件

    CreateFile

    2.2 创建内存映射文件

      HANDLE CreateFileMapping(

  HANDLE hFile, //文件句柄

      LPSECURITY_ATTRIBUTES lpFileMappingAttributes,

                             //安全属性

      DWORD flProtect, //保护模式

      DWORD dwMaximumSizeHigh,//大小的高32位

      DWORD dwMaximumSizeLow,    //大小的低32位

      LPCTSTR lpName ); //文件映射内核对象的名称

    2.3 映射成内存地址

    LPVOID MapViewOfFile(

       HANDLE hFileMappingObject, //文件映射句柄

       DWORD dwDesiredAccess, //访问模式

       DWORD dwFileOffsetHigh, //地址偏移高32位

       DWORD dwFileOffsetLow,//地址偏移低32位

       DWORD dwNumberOfBytesToMap ); //要映射的字节数



    2.4 使用内存

 对于大文件,可以用多次映射的方法达到访问的目的。有点像AWE技术。

 Windows只保证同一文件映射内核对象的多次映射的数据一致性,比如,当有两次映射同一对象到二个进程空间时,一个进程空间的数据改变后,另一个进程空间的数据也会跟着改变;不保证不同映射内核对象的多次映射的一致性。所以,使用文件映射时,最好在CreateFile时将共享模型设置为0独享,当然,对于只读文件没这个必要。



    2.5 卸载映射


       BOOL UnmapViewOfFile(


 

 LPCVOID lpBaseAddress //卸载的地址


       );


    2.6 关闭内存映射文件


       CloseHandle


    2.7 文件关闭


       CloseHandle



示例代码:

#include "stdafx.h"
#include "windows.h"

void Map( )
{	
	//创建文件
	HANDLE hFile = CreateFile( "C:\\map.dat",
		GENERIC_READ|GENERIC_WRITE,
		0, NULL, CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL, NULL );
	//创建文件映射
	HANDLE hMap = CreateFileMapping( hFile, NULL,
		PAGE_READWRITE, 0, 1024 * 1024, NULL );
	//映射地址
	CHAR * pszText = (CHAR *)MapViewOfFile( 
		hMap, FILE_MAP_ALL_ACCESS,
		0, 0, 1024 * 1024 );
	//使用内存
	strcpy( pszText, "Hello File Mapping" );
	printf( "%s\n", pszText );
	//卸载地址
	UnmapViewOfFile( pszText );
	//关闭文件映射
	CloseHandle( hMap );
	//关闭文件
	CloseHandle( hFile );
}

int main(int argc, char* argv[])
{
	Map( );
	return 0;
}