内存映射文件

内存映射文件（Memory-mapped file），或称“文件映射”、“映射文件”，是一段虚拟内存逐字节对应于一个文件或类文件的资源，使得应用程序处理映射部分如同访问主内存。

## 得益

主要用处是增加I/O性能，特别是用于大文件。对于小文件，内存映射文件会导致碎片空间浪费，因为内存映射总是要对齐页边界，最少耗费4 KiB。故一个5 KiB的文件将会映射占用8 KiB内存，浪费了3 KiB内存。访问内存映射文件比直接文件读写要快几个数量级。

内存映射文件可以只加载一部分内容到用户的逻辑内存空间。这对非常大的文件特别有用。

使用内存映射文件可以避免颠簸：把相当大的文件直接加载到内存时，由于可用内存不足，使得一边读取文件内存，同时把部分已经加载的文件从内存写入硬盘虚存文件中。

内存映射文件由操作系统的内存管理程序负责，因此绕过了硬盘虚存的分页文件（page file）。

## 分类

有两类内存映射文件：

### Persisted

Persisted文件与硬盘文件相关联，当关闭内存映射时，数据被写入对应的硬盘文件中。适合于很大的文件。

### Non-persisted

Non-persisted文件并不关联于硬盘文件。当关闭内存映射文件，所有数据被抛弃。适用于创建进程间通信的共享内存。

对于Windows操作系统，不需要调用CreateFile。调用CreateFileMapping时，将INVALID_HANDLE_VALUE作为hFile参数传入，指示创建的文件映射对象不是磁盘上的文件，而是页交换文件。所需分配的存储器大小由CreateFileMapping的dwMaximumSizeHigh和dwMaxinumSizeLow参数决定。

## 缺点

内存映射文件需要在进程的占用一块很大的连续逻辑地址空间。对于Intel的IA-32的4 GiB逻辑地址空间，可用的连续地址空间远远小于2---3 GiB。

相关联的文件的I/O错误（如可拔出驱动器或光驱被弹出，磁盘满时写操作等）的内存映射文件会向应用程序报告SIGSEGV/SIGBUS信号（POSIX环境）或EXECUTE_IN_PAGE_ERROR结构化异常（Windows环境）。通常的内存操作是无需考虑这些异常的。

有内存管理单元（MMU）才支持内存映射文件。

## 用途

最常见用途是绝大多数操作系统(包括Microsoft Windows与Unix-like系统)用于加载进程。

另一个用途是多个进程的共享内存。

第三个用途是对大文件的读写。

## 支持的平台

一些可移植的库实现：

- Boost.Interprocess, 在Boost C++ Libraries
- Boost.Iostreams,也在Boost C++ Libraries
- Fmstream
- Cpp-mmf

Java语言的java/nio/channels.

D语言的标准库（std.mmfile module）

Ruby语言的gem（库）Mmap.

Python语言的mmap标准库模块.

Perl的Sys::Mmap或File::Map.

Microsoft .NET的P/Invoke，或者Managed access(参见 Memory-Mapped Files ). 或第三方库API.

PHP的库函数file_get_contents( revision log ).

R语言的一个库 bigmemory 使用了Boost库的实现.

J语言至少自从2005年开始支持内存映射文件。它包括了对盒装的阵列数据和单一数据类型文件的支持。支持可以从`'data/jmf'`加载。J的Jdb和JD数据库引擎使用内存映射文件用于列存储。

### 类Unix

POSIX函数mmap,创建一个内存映射文件，需要提供文件描述符、开始位置的文件指针、映射长度等参数。or OpenVMS

### Windows

Windows API提供了一组函数以实现内存映射文件。

- 创建或打开文件内核对象HANDLE CreateFile(PCSTR pszFileName,DWORD dwDesiredAccess,DWORD dwShareMode,PSECURITY_ATTRIBUTES psa,DWORD dwCreationDisposition,DWORD dwFlagsAndAttributes,HANDLE hTemplateFile); 要实现内存文件的修改实时转换为磁盘文件的修改，对文件CreateFile时必须写权限。Windows加载PE文件时，会保证写时复制机制的使用。写时复制机制指的是同一个可执行文件的多个进程时，当其中一个进程要改写其映射文件的数据时，Windows会开辟一个新的页文件把要修改页的内容复制到该新页文件中并将新的页文件与当前进程相关联。
- dwDesiredAccess的值
- 0：不能读取或写入文件的内容。用于仅需要获得文件的属性时
- GENERIC_READ：可以从文件中读取数据
- GENERIC_WRITE：可以将数据写入文件
- GENERIC_READ|GENERIC_WRITE：可以从文件中读取数据，也可以将数据写入文件
- dwShareMode的值
- 0：打开文件的任何尝试均将失败
- FILE_SHARE_READ：使用GENERIC_WRITE打开文件的其他尝试将会失败
- FILE_SHARE_WRITE：使用GENERIC_READ打开文件的其他尝试将会失败
- FILE_SHARE_READ|FILE_SHARE_WRITE：打开文件的其他尝试将会取得成功
- 创建（或打开）一个文件映射内核对象HANDLE CreateFileMapping(HANDLE hFile,PSECURITY_ATTRIBUTES psa,DWORD fdwProtect,DWORD dwMaximumSizeHigh,DWORD dwMaximumSizeLow,PCTSTR pszName);
- 第一个参数hFile标识想要映射到进程地址空间中的文件句柄。该句柄由前面调用的CreateFile函数返回。
- 第二个参数psa参数指向文件映射内核对象的SECURITY_ATTRIBUTES结构的指针，通常传递的值是NULL（它提供默认的安全特性，返回的句柄是不能继承的）。
- PAGE_READONLY：当文件映射对象被映射时，可以读取文件的数据。必须已经将GENERIC_READ传递给CreateFile函数
- PAGE_READWRITE：当文件映射对象被映射时，可以读取和写入文件的数据。必须已经将GENERIC_READ | GENERIC_WRITE传递给CreateFile
- PAGE_WRITECOPY：当文件映射对象被映射时，可以读取和写入文件的数据。如果写入数据，会导致页面的私有拷贝得以创建。必须已经将GENERIC_READ或GENERIC_WRITE传递给CreateFile
- SEC_NOCACHE：节保护属性，告诉系统没有将文件的任何内存映射页面放入高速缓存
- SEC_IMAGE：节保护属性，映射的文件是个PE文件映像。系统确定PE文件各节的保护属性赋予文件映像的各个页面。例如， PE文件的代码节（.text）通常用PAGE_EXECUTE_READ属性，而PE文件的数据节(.data)通常用PAGE_READWRITE属性。
- SEC_RESERVE：节保护属性
- SEC_COMMIT：节保护属性
- 第四和五个参数dwMaximumSizeHigh和dwMaximumSizeLow：该文件的最大字节数
- 第六个参数pszName：以0结尾的字符串，给该文件映射对象赋予一个名字。该名字用于与其他进程共享文件映射对象。
- 文件数据映射到进程地址空间并提交PVOID MapViewOfFile(HANDLE hFileMappingObject,DWORD dwDesiredAccess,DWORD dwFileOffsetHigh,DWORD dwFileOffsetLow,SIZE_T dwNumberOfBytesToMap);
- 第一个参数hFileMappingObject标识文件映射对象的句柄，该句柄是前面调用CreateFileMapping或OpenFileMapping函数返回的。
- FILE_MAP_WRITE：可以读取和写入文件数据。CreateFileMapping函数必须通过传递PAGE_READWRITE标志来调用
- FILE_MAP_READ：可以读取文件数据。CreateFileMapping函数可以通过传递下列任何一个保护属性来调用：PAGE_READONLY、PAGE_ READWRITE或PAGE_WRITECOPY
- FILE_MAP_ALL_ACCESS：与FILE_MAP_WRITE相同
- FILE_MAP_COPY：可以读取和写入文件数据。如果写入文件数据，可以创建一个页面的私有拷贝。CreateFileMapping函数可以用PAGE_READONLY、PAGE_READWRITE或PAGE_WRITECOPY等保护属性中的任何一个来调用。
- 第三、四个参数dwFileOfsetHigh和dwFileOfsetLow：指定从哪个字节开始作为视图中的第一个字节来映射。
- 第五个参数dwNumberOfBytesToMap指出多少字节要映射到地址空间。如果设定的值是0，那么系统将设法把从文件中的指定位移开始到整个文件的结尾的视图映射到地址空间。
- 从进程地址空间撤消文件映射BOOL UnmapViewOfFile(PVOID pvBaseAddress);
- 参数pvBaseAddress由MapViewOfFile函数返回。
- 强制系统将修改过的数据重新写入磁盘BOOL FlushViewOfFile(PVOID pvAddress,SIZE_T dwNumberOfBytesToFlush);
- 第一个参数是内存映射文件视图的一个字节的地址。
- 第二个参数指明要刷新的字节数。
- 关闭文件映射对象：用CloseHandle函数
- 文件对象：用CloseHandle函数