引言 檔案操作是應用程式最為基本的功能之一,Win32 API和MFC均提供有支援檔案處理的函數和類,常用的有Win32 API的CreateFile()、WriteFile()、ReadFile()和MFC提供的CFile類等。一般來說,以上這些函數可以滿足大多數場合的要求,但是對于某些特殊應用領域所需要的動辄幾十GB、幾百GB、乃至幾TB的海量存儲,再以通常的檔案處理方法進行處理顯然是行不通的。目前,對于上述這種大檔案的操作一般是以記憶體映射檔案的方式來加以處理的,本文下面将針對這種Windows核心程式設計技術展開讨論。
記憶體映射檔案概述
記憶體檔案映射也是Windows的一種記憶體管理方法,提供了一個統一的記憶體管理特征,使應用程式可以通過記憶體指針對磁盤上的檔案進行通路,其過程就如同對加載了檔案的記憶體的通路。通過檔案映射這種使磁盤檔案的全部或部分内容與程序虛拟位址空間的某個區域建立映射關聯的能力,可以直接對被映射的檔案進行通路,而不必執行檔案I/O操作也無需對檔案内容進行緩沖處理。記憶體檔案映射的這種特性是非常适合于用來管理大尺寸檔案的。
在使用記憶體映射檔案進行I/O處理時,系統對資料的傳輸按頁面來進行。至于内部的所有記憶體頁面則是由虛拟記憶體管理器來負責管理,由其來決定記憶體頁面何時被分頁到磁盤,哪些頁面應該被釋放以便為其它程序提供空閑空間,以及每個程序可以擁有超出實際配置設定實體記憶體之外的多少個頁面空間等等。由于虛拟記憶體管理器是以一種統一的方式來處理所有磁盤I/O的(以頁面為機關對記憶體資料進行讀寫),是以這種優化使其有能力以足夠快的速度來處理記憶體操作。
使用記憶體映射檔案時所進行的任何實際I/O互動都是在記憶體中進行并以标準的記憶體位址形式來通路。磁盤的周期性分頁也是由作業系統在背景隐蔽實作的,對應用程式而言是完全透明的。記憶體映射檔案的這種特性在進行大檔案的磁盤事務操作時将獲得很高的效益。
需要說明的是,在系統的正常的分頁操作過程中,記憶體映射檔案并非一成不變的,它将被定期更新。如果系統要使用的頁面目前正被某個記憶體映射檔案所占用,系統将釋放此頁面,如果頁面資料尚未儲存,系統将在釋放頁面之前自動完成頁面資料到磁盤的寫入。
對于使用頁虛拟存儲管理的Windows作業系統,記憶體映射檔案是其内部已有的記憶體管理元件的一個擴充。由可執行代碼頁面和資料頁面組成的應用程式可根據需要由作業系統來将這些頁面換進或換出記憶體。如果記憶體中的某個頁面不再需要,作業系統将撤消此頁面原擁用者對它的控制權,并釋放該頁面以供其它程序使用。隻有在該頁面再次成為需求頁面時,才會從磁盤上的可執行檔案重新讀入記憶體。同樣地,當一個程序初始化啟動時,記憶體的頁面将用來存儲該應用程式的靜态、動态資料,一旦對它們的操作被送出,這些頁面也将被備份至系統的頁面檔案,這與可執行檔案被用來備份執行代碼頁面的過程是很類似的。圖1展示了代碼頁面和資料頁面在磁盤存儲器上的備份過程:
圖1 程序的代碼頁、資料頁在磁盤存儲器上的備份
顯然,如果可以采取同一種方式來處理代碼和資料頁面,無疑将會提高程式的執行效率,而記憶體映射檔案的使用恰恰可以滿足此需求。
// 選擇檔案
CFileDialog fileDlg(TRUE, "*.txt", "*.txt", NULL, "文本檔案 (*.txt)|*.txt||", this);
fileDlg.m_ofn.Flags |= OFN_FILEMUSTEXIST;
fileDlg.m_ofn.lpstrTitle = "通過記憶體映射檔案讀取資料";
if (fileDlg.DoModal() == IDOK)
{
// 建立檔案對象
HANDLE hFile = CreateFile(fileDlg.GetPathName(), GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
TRACE("建立檔案對象失敗,錯誤代碼:%drn", GetLastError());
return;
}
// 建立檔案映射對象
HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
if (hFileMap == NULL)
{
TRACE("建立檔案映射對象失敗,錯誤代碼:%drn", GetLastError());
return;
}
// 得到系統配置設定粒度
SYSTEM_INFO SysInfo;
GetSystemInfo(&SysInfo);
DWORD dwGran = SysInfo.dwAllocationGranularity;
// 得到檔案尺寸
DWORD dwFileSizeHigh;
__int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);
qwFileSize |= (((__int64)dwFileSizeHigh) << 32);
// 關閉檔案對象
CloseHandle(hFile);
// 偏移位址
__int64 qwFileOffset = 0;
// 塊大小
DWORD dwBlockBytes = 1000 * dwGran;
if (qwFileSize < 1000 * dwGran)
dwBlockBytes = (DWORD)qwFileSize;
while (qwFileOffset > 0)
{
// 映射視圖
LPBYTE lpbMapAddress = (LPBYTE)MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS,
(DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF),
dwBlockBytes);
if (lpbMapAddress == NULL)
{
TRACE("映射檔案映射失敗,錯誤代碼:%drn", GetLastError());
return;
}
// 對映射的視圖進行通路
for(DWORD i = 0; i < dwBlockBytes; i++)
BYTE temp = *(lpbMapAddress + i);
// 撤消檔案映像
UnmapViewOfFile(lpbMapAddress);
// 修正參數
qwFileOffset += dwBlockBytes;
qwFileSize -= dwBlockBytes;
}
// 關閉檔案映射對象句柄
CloseHandle(hFileMap);
AfxMessageBox("成功完成對檔案的通路");
}