内存映射文件

**記憶體對映檔案**（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 語言的 **`FileChannel`**.

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（它提供預設的安全特性，返回的控制代碼是不能繼承的）。
  * 第三個參數 fdwProtect 設定保護屬性：
    * 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 函式返回的。
  * 第二個參數 dwDesiredAccess 標識如何存取該資料:
    * 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 函式

## 參見

* 記憶體對映輸入輸出

## 參考文獻