内存映射文件——Windows核心编程学习手札之十七
內存映射文件
——Windows核心編程學習手札之十七
與虛擬內存一樣,內存映射文件保留地址空間,并將物理存儲器提交給該區域,差別在于所提交的物理存儲器是磁盤上有文件存在的空間,而非系統的頁文件,一旦文件被映射,就可以訪問它,如同整個文件被加載到內存一樣。內存映射文件用于三個不同目的:
1)系統使用內存映射文件,以便加載和執行.exe和DLL文件,可以節省頁文件空間和應用程序啟動運行所需的時間;
2)可以使用內存映射文件來訪問磁盤上的數據文件,可以不必對文件執行I/O操作,并且可以不用對文件內容進行緩存;
3)使用內存映射文件,使同一臺計算機上運行的多個進程能夠相互之間共享數據。
當線程調用CreateProcess時,系統會執行下列步驟:
1)系統找出在調用CreateProcess時設定的.exe文件,如找不到這個文件,進程無法創建,CreateProcess將返回FALSE;
2)系統創建一個新進程內核對象;
3)系統為新進程創建一個私有地址空間;
4)系統保留一個足夠大的地址空間區域,用于存放.exe文件,按照默認設置,.exe文件的基地址是0X00400000,也可以在創建應用程序的.exe文件時重載這個地址,方法是在鏈接應用程序時使用鏈接程序的/BASE選項。
5)系統知道已保留區域的物理存儲器是在磁盤上的.exe文件,而不是在系統的頁文件;
當.exe文件被映射到進程的地址空間后,系統將訪問.exe文件一個部分,該部分列出了包含.exe文件中的代碼要調用的函數的DLL文件,進而系統為每個DLL文件調用LoadLibrary函數。通過LoadLibrary函數加載DLL,系統有如下步驟:
1)系統保留一個足夠大的地址空間區域,用于存放DLL文件,區域位置由DLL文件本身決定,默認設置,Microsoft的Visual C++建立的DLL文件基地址是0X10000000,不過,你可以在創建DLL文件重載這個地址,方法是使用鏈接程序的/BASE選項。Windows提供的所有標準系統DLL都擁有不同的基地址,這樣,如果加載到單個地址空間,區域不會重疊;
2)系統可能因為區域為其他.exe或DLL文件占用和區域不夠大的原因,無法在該DLL的首選基地址;
3)系統知道支持已保留區域的物理存儲器位于磁盤上的DLL文件中,而不是在系統的頁文件中;
如果由于某種原因,系統無法映射.exe和所有必要的DLL文件,那系統會通過消息框提示,并且釋放進程的地址空間和進程對象。CreateProcess函數將返回FALSE,可通過GetLastError函數了解失敗原因。當.exe文件和所有必要DLL文件都成功映射到進程的地址空間后,系統開始執行.exe文件的啟動代碼,同時,系統將負責所有的分頁、緩沖和高速緩存的處理,如.exe文件中的代碼需要去訪問尚未加載到內存的指令地址,將出現錯誤,系統發現錯誤后,將自動將該頁代碼從該文件的映象加載到一個RAM頁面,之后系統將這個RAM頁面映射到進程的地址空間中的相應位置,并且讓線程繼續運行。
知道應用程序實例數代碼:
// AppInst.cpp : Defines the entry point for the application.
//
?
#include "stdafx.h"
#include "resource.h"
?
#define MAX_LOADSTRING 100
?
// Global Variables:
HINSTANCE hInst;??????????????????????????????????????????????????????? // current instance
TCHAR szTitle[MAX_LOADSTRING];??????????????????????????????????????????????????????? // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];????????????????????????????????????????????????? // The title bar text
?
// Foward declarations of functions included in this code module:
ATOM????????????????????????? MyRegisterClass(HINSTANCE hInstance);
BOOL????????????????????????? InitInstance(HINSTANCE, int);
LRESULT CALLBACK? WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK? About(HWND, UINT, WPARAM, LPARAM);
?
//define here for demo
UINT g_uMsgAppInstCountUpdate=INVALID_ATOM;//the system-wide unique window message
//==========================================
//tell the compiler to put this initialized variable in tis own shared section
//so it is shared by all instances of this application.
#pragma data_seg("Shared")
volatile LONG g_lApplicationInstances=0;
#pragma data_seg()
//tell the linker to make the Shared section readable/writable,and shared.
#pragma comment(linker,"/Section:Shared,RWS")
//=============================================
//end of demo
?
int APIENTRY WinMain(HINSTANCE hInstance,
???????????????????? HINSTANCE hPrevInstance,
???????????????????? LPSTR???? lpCmdLine,
???????????????????? int?????? nCmdShow)
{
??? //get the numberic value of the systemwide window message used to notify
?????? //all top-level windows when the module's usage count has changed.
??? g_uMsgAppInstCountUpdate=RegisterWindowMessage(TEXT("MsgAppInstCountUpdate"));
?????? //there is another instance of this application running.
?????? InterlockedExchangeAdd((PLONG)&g_lApplicationInstances,1);
????? // TODO: Place code here.
?????? MSG msg;
?????? HACCEL hAccelTable;
?
?????? // Initialize global strings
?????? LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
?????? LoadString(hInstance, IDC_APPINST, szWindowClass, MAX_LOADSTRING);
?????? MyRegisterClass(hInstance);
?
?????? // Perform application initialization:
?????? if (!InitInstance (hInstance, nCmdShow))
?????? {
????????????? return FALSE;
?????? }
?
?????? hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_APPINST);
?
?????? // Main message loop:
?????? while (GetMessage(&msg, NULL, 0, 0))
?????? {
????????????? if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
????????????? {
???????????????????? TranslateMessage(&msg);
???????????????????? DispatchMessage(&msg);
????????????? }
?????? }
?
??? InterlockedExchangeAdd((PLONG)&g_lApplicationInstances,-1);
?????? //have all other instances update their display.
??? PostMessage(HWND_BROADCAST,g_uMsgAppInstCountUpdate,0,0);
?
?????? return msg.wParam;
}
?
?
?
//
//? FUNCTION: MyRegisterClass()
//
//? PURPOSE: Registers the window class.
//
//? COMMENTS:
//
//??? This function and its usage is only necessary if you want this code
//??? to be compatible with Win32 systems prior to the 'RegisterClassEx'
//??? function that was added to Windows 95. It is important to call this function
//??? so that the application will get 'well formed' small icons associated
//??? with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
?????? WNDCLASSEX wcex;
?
?????? wcex.cbSize = sizeof(WNDCLASSEX);
?
?????? wcex.style??????????????????? = CS_HREDRAW | CS_VREDRAW;
?????? wcex.lpfnWndProc?????? = (WNDPROC)WndProc;
?????? wcex.cbClsExtra?????????? = 0;
?????? wcex.cbWndExtra???????? = 0;
?????? wcex.hInstance???????????? = hInstance;
?????? wcex.hIcon????????????????? = LoadIcon(hInstance, (LPCTSTR)IDI_APPINST);
?????? wcex.hCursor??????? = LoadCursor(NULL, IDC_ARROW);
?????? wcex.hbrBackground???? = (HBRUSH)(COLOR_WINDOW+1);
?????? wcex.lpszMenuName???? = (LPCSTR)IDC_APPINST;
?????? wcex.lpszClassName???? = szWindowClass;
?????? wcex.hIconSm???????????? = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
?
?????? return RegisterClassEx(&wcex);
}
?
//
//?? FUNCTION: InitInstance(HANDLE, int)
//
//?? PURPOSE: Saves instance handle and creates main window
//
//?? COMMENTS:
//
//??????? In this function, we save the instance handle in a global variable and
//??????? create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
?? HWND hWnd;
?
?? hInst = hInstance; // Store instance handle in our global variable
?
?? hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
????? CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
?
?? if (!hWnd)
?? {
????? return FALSE;
?? }
?
?? ShowWindow(hWnd, nCmdShow);
?? UpdateWindow(hWnd);
?
?? //Force the static control to be initializede correctly.
?? PostMessage(HWND_BROADCAST,g_uMsgAppInstCountUpdate,0,0);
?
?? return TRUE;
}
?
//
//? FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
// ?PURPOSE:? Processes messages for the main window.
//
//? WM_COMMAND??? - process the application menu
//? WM_PAINT???? - Paint the main window
//? WM_DESTROY????? - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
?????? int wmId, wmEvent;
?????? PAINTSTRUCT ps;
?????? HDC hdc;
?????? TCHAR szHello[MAX_LOADSTRING];
?????? LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
???
??? if(message==g_uMsgAppInstCountUpdate)
?????? {
????????????? TCHAR chNum[4];
????????????? itoa(g_uMsgAppInstCountUpdate,chNum,4);
????????????? strcat(szHello,chNum);
?????? }
?
?????? switch (message)
?????? {
????????????? case WM_COMMAND:
???????????????????? wmId??? = LOWORD(wParam);
???????????????????? wmEvent = HIWORD(wParam);
???????????????????? // Parse the menu selections:
???????????????????? switch (wmId)
???????????????????? {
??????????????????????????? case IDM_ABOUT:
??????????????????????????? ?? DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
??????????????????????????? ?? break;
??????????????????????????? case IDM_EXIT:
??????????????????????????? ?? DestroyWindow(hWnd);
??????????????????????????? ?? break;
??????????????????????????? default:
??????????????????????????? ?? return DefWindowProc(hWnd, message, wParam, lParam);
???????????????????? }
???????????????????? break;
????????????? case WM_PAINT:
???????????????????? hdc = BeginPaint(hWnd, &ps);
???????????????????? // TODO: Add any drawing code here...
???????????????????? RECT rt;
???????????????????? GetClientRect(hWnd, &rt);
???????????????????? DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
???????????????????? EndPaint(hWnd, &ps);
???????????????????? break;
????????????? case WM_DESTROY:
???????????????????? PostQuitMessage(0);
???????????????????? break;
????????????? default:
???????????????????? return DefWindowProc(hWnd, message, wParam, lParam);
?? }
?? return 0;
}
?
// Mesage handler for about box.
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
?????? switch (message)
?????? {
????????????? case WM_INITDIALOG:
??????????????????????????? return TRUE;
?
????????????? case WM_COMMAND:
???????????????????? if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
???????????????????? {
??????????????????????????? EndDialog(hDlg, LOWORD(wParam));
??????????????????????????? return TRUE;
???????????????????? }
???????????????????? break;
?????? }
??? return FALSE;
}
操作系統使內存能夠將一個數據文件映射到進程的地址空間中,方便大量數據的操作。下面以將文件中的所有字節順序進行倒序,來理解內存映射文件的功能。
步驟一:創建或打開文件內核對象
若要創建或打開一個文件內核對象,調用CreateFile函數:
HANDLE CreateFile(
???????????????? PCSTR pszFileName,
???????????????? DWORD dwDesiredAccess,
???????????????? DWORD dwShareMode,
???????????????? PSECURITY_ATTRIBUTES psa,
???????????????? DWORD dwCreationDisposition,
???????????????? DWORD dwFlagsAndAttributes,
???????????????? HANDLE hTemplateFile);
參數pszFileName用于指明要創建或打開的文件的名字(包括嚴格選項路徑),參數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表示打開文件的其他嘗試將會取得成功。如果CreateFile函數成功地創建或打開指定的文件,便返回個億 文件內核對象的句柄,否則返回INVALID_HANDLE_VALUE。
步驟二:創建一個文件映射內核對象
調用CreateFile函數,就可將文件映象的物理存儲器的位置告訴系統,所傳遞的路徑名用于指明支持文件映象的物理存儲器所在磁盤上的確切位置,此時,還需要告訴系統,文件映射對象需要多少物理存儲器,需調用CreateFileMapping函數:
HANDLE CreateFileMapping(
??????????????????????? HANDLE hFile,
??????????????????????? PSECURITY_ATTRIBUTES psa,
??????????????????????? DWORD fdwProtect,
????????????????? ??????DWORD dwMaximumSizeHigh,
??????????????????????? DWORD dwMaximumSizeLow,
??????????????????????? PCTSTR pszName);
參數hFile用于標識映射到進程地址空間中的文件句柄,是CreateFile返回的值;psa參數是指向文件映射內核對象的SECURITY_ATTRIBUTES結構的指針,通常傳遞NULL值。系統創建文件映射對象,并將用于標識該對象的句柄返回該調用線程,如系統無法創建文件映射對象,便返回一個NULL句柄值。
步驟三:將文件數據映射到進程的地址空間
當創建了一個文件映射對象后,仍然必須讓系統為文件的數據保留一個地址空間區域,并將文件的數據作為映射到該區域的物理存儲器進行提交。可通過MapViewOfFile函數來實現:
?????? PVOID MapViewOfFile(
??????????????????????? HNADLE hFileMappingObject,
??????????????????????? DWORD dwDesiredAccess,
?????????? ?????????????DWORD dwFileOffsetHigh,
??????????????????????? DWORD dwFileOffsetLow,
??????????????????????? SIZE_T dwNumberOfBytesToMap);
參數hFileMappingObject用于標識文件映射對象的句柄,該句柄上調用CreateFileMapping和OpenFileMapping函數返回的。參數dwDesiredAccess用于標識如何訪問該數據。
步驟四:從進程的地址空間中撤消文件數據的映象
當不再需要保留映射到你的進程地址空間區域中的文件數據時,可調用下面函數:
?????? BOOL UnmapViewOfFile(PVOID pvBaseAddress);
參數pvBaseAddress用于設定返回區域的基地址,該值必須與調用MapViewOfFile函數返回的值相同。在進程終止運行前,如沒有調用該函數,保留區域將不會被釋放。為提高速度,系統將文件的數據頁面進行高速緩存,并且在對文件的映射視圖進行操作時不立即更新文件的磁盤映象,如果要確保更新被寫入磁盤,可調用函數FlushViewOfFile強制系統將修改過的數據的一部分或全部重新寫入磁盤映像中:
?????? BOOL FlushViewOfFile(
??????????????????????? PVOID pvAddress,
??????????????????????? SIZE_T dwNumberOfBytesToFlush);
第一個參數pvAddress包含在內存映射文件中的視圖的一個字節的地址,傳遞地址圓整為一個頁面邊界值,第二個參數指明要刷新的字節數。
步驟五和步驟六:關閉文件映射對象和文件對象
如不關閉打開了的內核對象,在進程繼續運行中會導致資源泄露。關閉文件映射對象和文件對象,需兩次調用CloseHandle函數,每個句柄調用一次。
使用內存映射文件在進程之間共享數據。Windows提供各種機制,使應用程序能共享數據和信息,這些機制包括:RPC、COM、OLE、DDE、窗口消息(尤其是WM_COPYDATA)、剪貼板、郵箱、管道和套接字等。在Windows中,單機共享數據的最底層機制是內存映射文件。如果互相進行通信的所有進程都在同一臺計算機上,上面提到的所有機制均使用內存映射文件從事,如要求達到較高性能和較小開銷,內存映射文件是最佳機制。數據共享方法是通過讓兩個或多個進程映射同一個文件映射對象的視圖來實現的,這意味著它們將共享物理存儲器的同一個頁面,因此,當一個進程將數據寫入一個共享文件映射對象的視圖時,其他進程可以立即看到它們視圖中的數據變更情況。注意,如果多個進程共享單個文件映射對象,那所有進程必須使用相同的名字來表示該文件映射對象。
總結
以上是生活随笔為你收集整理的内存映射文件——Windows核心编程学习手札之十七的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2009-08-19股市大跌分析(转载)
- 下一篇: 堆栈——Windows核心编程学习手札之