1、前言
Windows提供了3種進行內存管理的方法:
• 虛擬內存,最適合用來管理大型對象或結構數組。
• 內存映射文件,最適合用來管理大型數據流(通常來自文件)以及在單個計算機上運行的多個進程之間共享數據。
• 內存堆棧,最適合用來管理大量的小對象。
內存映射文件可以用於3個不同的目的
• 系統使用內存映射文件,以便加載和執行. exe和DLL文件。這可以大大節省頁文件空間和應用程序啓動運行所需的時間。
• 可以使用內存映射文件來訪問磁盤上的數據文件。這使你可以不必對文件執行I/O操作,並且可以不必對文件內容進行緩存。
• 可以使用內存映射文件,使同一臺計算機上運行的多個進程能夠相互之間共享數據。Windows確實提供了其他一些方法,以便在進程之間進行數據通信,但是這些方法都是使用內存映射文件來實現的,這使得內存映射文件成爲單個計算機上的多個進程互相進行通信的最有效的方法。
2、使用內存映射數據文件
若要使用內存映射文件,必須執行下列操作步驟:
1) 創建或打開一個文件內核對象,該對象用於標識磁盤上你想用作內存映射文件的文件。
2) 創建一個文件映射內核對象,告訴系統該文件的大小和你打算如何訪問該文件。
3) 讓系統將文件映射對象的全部或一部分映射到你的進程地址空間中。
當完成對內存映射文件的使用時,必須執行下面這些步驟將它清除:
1) 告訴系統從你的進程的地址空間中撤消文件映射內核對象的映像。
2) 關閉文件映射內核對象。
3) 關閉文件內核對象。
2.1 創建或打開文件內核對象
HANDLE CreateFile(
PCSTR pszFileName, /*文件路徑*/
DWORD dwDesiredAccess, /*請求權限*/
DWORD dwShareMode, /*共享模式*/
PSECURITY_ATTRIBUTES psa, /*安全特性*/
DWORD dwCreationDisposition, /*創建動作*/
DWORD dwFlagsAndAttributes, /*文件屬性和標記*/
HANDLE hTemplateFile /*模板文件*/
);
1)pszFileName:指示文件路徑名
2)dwDesiredAccess:指示文件權限
值 | 含義 |
0 | 不能讀取或寫入文件的內容。當只想獲得文件的屬性時,請設定0 |
GENERIC_READ | 可以從文件中讀取數據 |
GENERIC_WRITE | 可以將數據寫入文件 |
GENERIC_READ | GENERIC_WRITE | 可以從文件中讀取數據,也可以將數據寫入文件 |
3)dwShareMode:文件共享模式
值 | 含義 |
0 | 打開文件的任何嘗試均將失敗 |
FILE_SHARE_READ | 使用GENERIC_WRITE打開文件的其他嘗試將會失敗 |
FILE_SHARE_WRITE | 使用GENERIC_READ打開文件的其他嘗試將會失敗 |
FILE_SHARE_READ | FILE_SHARE_WRITE | 打開文件的其他嘗試將會取得成功 |
4)psa:指向文件映射內核對象的SECURITY_ATTRIBUTES結構的指針,通常傳遞的值是NULL(它提供默認的安全特性,返回的句柄是不能繼承的)。
5)dwCreationDisposition:指示當文件存在或不存在時的動作。
值 | 含義 |
CREATE_ALWAYS | 始終創建爲新文件,如果已存在將重寫文件 |
CREATE_NEW | 僅當文件不存在時創建新文件 |
OPEN_ALWAYS | 總是打開文件,如果不存在將創建一個文件並打開 |
OPEN_EXISTING | 僅當文件存在時,打開文件 |
TRUNCATE_EXISTING | 僅當文件存在時,打開文件並清空數據 |
6)dwFlagsAndAttributes:文件屬性,最常用默認值:FILE_ATTRIBUTE_NORMAL (參考MSDN)
7)hTemplateFile:指示模板文件。可爲空,當打開已存在文件時,忽略該參數。
2.2 創建一個文件映射內核對象
調用CreateFileMapping函數告訴系統,文件映射對象需要多少物理存儲器
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD fdwProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName
);
1)hFile:用於標識你想要映射到進程地址空間中的文件句柄。該句柄由前面調用的CreateFile函數返回。
2)psa:指向文件映射內核對象的SECURITY_ATTRIBUTES結構的指針,通常傳遞的值是NULL(它提供默認的安全特性,返回的句柄是不能繼承的)。
3)fdwProtect:使你能夠設定這些保護屬性。大多數情況下,可以設定下表列出的3個保護屬性之一:
使用fdwProtect 參數設定的部分保護屬性:
保護屬性 | 含義 |
PAGE_READONLY | 當文件映射對象被映射時,可以讀取文件的數據。必須已經將GENERIC_READ傳遞給CreateFile函數 |
PAGE_READWRITE | 當文件映射對象被映射時,可以讀取和寫入文件的數據。必須已經將GENERIC_READ | GENERIC_WRITE傳遞給CreateFile |
PAGE_WRITECOPY | 當文件映射對象被映射時,可以讀取和寫入文件的數據。如果寫入數據,會導致頁面的私有拷貝得以創建。必須已經將GENERIC_READ或GENERIC_WRITE傳遞給CreateFile |
除了上面的頁面保護屬性外,還有4個節保護屬性:
節的第一個保護屬性是SEC_NOCACHE,它告訴系統,沒有將文件的任何內存映射頁面放入高速緩存。因此,當將數據寫入該文件時,系統將更加經常地更新磁盤上的文件數據。供設備驅動程序開發人員使用的,應用程序通常不使用。
節的第二個保護屬性是SEC_IMAGE,它告訴系統,你映射的文件是個可移植的可執行(PE)文件映像。當系統將該文件映射到你的進程的地址空間中時,系統要查看文件的內容,以確定將哪些保護屬性賦予文件映像的各個頁面。例如, PE文件的代碼節( . text)通常用PAGE_ EXECUTE_READ屬性進行映射, 而PE 文件的數據節( .data) 則通常用PAGE_READW RITE屬性進行映射。如果設定的屬性是S E C _ I M A G E,則告訴系統進行文件映像的映射,並設置相應的頁面保護屬性。
最後兩個保護屬性是SEC_RESERVE和SEC_COMMIT,它們是兩個互斥屬性。只有當創建由系統的頁文件支持的文件映射對象時,這兩個標誌纔有意義。SEC_COMMIT標誌能使CreateFileMapping從系統的頁文件中提交存儲器。如果兩個標誌都不設定,其結果也一樣。
4,5)dwMaximumSizeHigh和dwMaximumSizeLow這兩個參數將告訴系統該文件的最大字節數
6)pszName: 它是個以0結尾的字符串,用於給該文件映射對象賦予一個名字。該名字用於與其他進程共享文件映射對象。
2.3 將文件數據映射到進程的地址空間
將文件的數據作爲映射到該區域的物理存儲器進行提交。
PVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap
);
1)hFileMappingObject:用於標識文件映射對象的句柄,該句柄是前面調用CreateFileMapping或OpenFileMapping函數返回的。
2)dwDesiredAccess:用於標識如何訪問該數據。可以設定下表所列的4個值中的一個。
值 | 含義 |
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 | 可以讀取和寫入文件數據。如果寫入文件數據,可以創建一個頁面的私有拷貝。在Windows 2000中,CreateileMapping函數可以用PAGE_READONLY、PAGE_READWRITE或PAGE_WRITECOPY等保護屬性中的任何一個來調用。在Windows 98中,CreateFileMapping必須用PAGE_WRITECOPY來調用 |
(一個文件映射到你的進程的地址空間中時,你不必一次性地映射整個文件。相反,可以只將文件的一小部分映射到地址空間。被映射到進程的地址空間的這部分文件稱爲一個視圖。)
3,4)dwFileOfsetHigh和dwFileOfsetLow參數。指定哪個字節應該作爲視圖中的第一個字節來映射。
5)dwNumberOfBytesToMap有多少字節要映射到地址空間。如果設定的值是0,那麼系統將設法把從文件中的指定位移開始到整個文件的結尾的視圖映射到地址空間。
2.4 從進程的地址空間中撤消文件數據的映像
當不再需要保留映射到進程地址空間區域中的文件數據時,可以通過調用下面的函數將它釋放:
BOOL UnmapViewOfFile(PVOID pvBaseAddress);
參數:pvBaseAddress由MapViewOfFile函數返回。
注意:如果沒有調用這個函數,那麼在進程終止運行前,保留的區域就不會被釋放。每當調用MapViewOfFile時,系統總是在你的進程地址空間中保留一個新區域,而以前保留的所有區域將不被釋放。
爲了提高速度,系統將文件的數據頁面進行高速緩存,並且在對文件的映射視圖進行操作時不立即更新文件的磁盤映像。如果需要確保你的更新被寫入磁盤,可以強制系統將修改過的數據的一部分或全部重新寫入磁盤映像中,方法是調用FlushViewOfFile函數:
BOOL FlushViewOfFile(
PVOID pvAddress,
SIZE_T dwNumberOfBytesToFlush
);
1)pvAddress:包含在內存映射文件中的視圖的一個字節的地址。
2)dwNumberOfBytesToFlush:指明你想要刷新的字節數。系統將把這個數字向上取整,使得字節總數是頁面的整數。
如果你調用FlushViewOfFile函數並且不修改任何數據,那麼該函數只是返回,而不將任何信息寫入磁盤。
2.5 關閉文件映射對象和文件對象
用CloseHandle函數關閉相應的文件對象和文件映射對象句柄。
HANDLE hFile = CreateFile(...);
HANDLE hFileMapping = CreateFileMapping(hFile, ...);
CloseHandle(hFile);
PVOID pvFile = MapViewOfFile(hFileMapping, ...);
CloseHandle(hFileMapping);
// Use the memory-mapped file.
UnmapViewOfFile(pvFile);
3、相關問題
3.1 內存映射文件與數據視圖的相關性
系統允許映射一個文件的相同數據的多個視圖。只要映射相同的文件映射對象,系統就會確保映射的視圖數據的相關性。
例如,如果你的應用程序改變了一個視圖中的文件內容,那麼所有其他視圖均被更新以反映這個變化。這是因爲儘管頁面多次被映射到進程的虛擬地址空間,但是系統只將數據放在單個RAM頁面上。如果多個進程映射單個數據文件的視圖,那麼數據仍然是相關的,因爲在數據文件中,每個RAM頁面只有一個實例——正是這個RAM頁面被映射到多個進程的地址空間。
注意:Windows允許創建若干個由單個數據文件支持的文件映射對象。Windows不能保證這些不同的文件映射對象的視圖具有相關性。它只能保證單個文件映射對象的多個視圖具有相關性。
3.2 頁文件支持的內存映射文件
它不必調用CreateFile函數,只需要調用CreateFileMapping函數,並且傳遞INVALID_HANDLE_VALUE作爲hFile參數。這將告訴系統,你不是創建其物理存儲器駐留在磁盤上的文件中的文件映射對象,相反,你想讓系統從它的頁文件中提交物理存儲器。分配的存儲器的數量由CreateFileMapping函數的dwMaximumSizeHigh和dwMaximumSizeLow兩個參數來決定。當創建了文件映射對象並且將它的一個視圖映射到進程的地址空間之後,就可以像使用任何內存區域那樣使用它。
如果你想要與其他進程共享該數據,可調用CreateFileMapping函數,並傳遞一個以0結尾的字符串作爲pszName參數。然後,想要訪問該存儲器的其他進程就可以調用CreateFileMapping或OpenFileMapping函數,並傳遞相同的名字。
當進程不再想要訪問文件映射對象時,該進程應該調用CloseHandle函數。當所有句柄均被關閉後,系統將從系統的頁文件中收回已經提交的存儲器。
3.3 用內存映射文件在進程之間共享數據
數據共享方法是通過讓兩個或多個進程映射同一個文件映射對象的視圖來實現的,這意味着它們將共享物理存儲器的同一個頁面。因此,當一個進程將數據寫入一個共享文件映射對象的視圖時,其他進程可以立即看到它們視圖中的數據變更情況。
注意:如果多個進程共享單個文件映射對象,那麼所有進程必須使用相同的名字來表示該文件映射對象。