記憶體映射文件

記憶體映射文件（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函數