【面經筆記】內存映射、共享內存

知道共享內存嗎?

額,是把同一塊內存映射到兩個進程的地址空間?。。。。。。

(還不如說不知道)


內存映射

內存映射主要應用於三種情況:

  • 系統使用內存映射文件載入EXE,DLL文件。這節省了大量頁交換文件的空間以及程序啓動時間。
  • 開發人員使用內存映射文件來訪問磁盤上的數據文件,這使得我們避免直接對文件I/O操作和文件內容的緩存
  • 通過使用內存映射文件,可以實現不同進程間的共享數據(進程間共享內存通信)

創建一個內存映射文件相當於先預訂一塊地址空間區域,然後再給區域調撥物理存儲器。唯一的不同在於內存映射文件的物理存儲器來自磁盤上的文件,而不是系統的頁交換文件中分配(內存)。


轉載可執行文件:

CreateProcess時,系統執行以下步驟:

1、找到exe文件位置
2、系統創建新的內核對象
3、系統爲新進程創建私有地址空間
4、系統在地址空間預定一塊足夠大區域容納exe文件,默認基地址0x400000。
5、系統會標註exe所在的地址空間區域。表面該區域的後備物理存儲器來自磁盤的文件而不是來自系統的頁交換文件。(即在磁盤,不在內存

6、當系統把exe文件映射到進程地址空間後,會訪問exe的一個段,獲取DLL加載信息,系統調用LoadLibrary()加載每個DLL。每次加載DLL步驟都類似上述4、5。

區別是exe是第一個加載的模塊,基地址不會被佔用,而DLL的基地址可能已經被佔用,需要對DLL進行重定位,修改部分指令。

系統同樣會標註DLL的地址空間區域,表明該區域的後備物理存儲器來自磁盤的DLL文件,而不是系統的頁交換文件。但是如果執行了重定位,則會標註被修改的部分物理存儲器被映射到了頁交換文件。


同一可執行文件/dll 的多個實例不會共享全局/靜態數據

通過內存映射文件,同一應用程序的多個實例可以共享內存中的代碼和數據。

系統通過寫時複製特性防止應用程序的一個實例修改了數據頁面的全部變量,影響其他實例。

寫時複製:任何程序試圖寫入內存映射文件時,系統首先截獲,然後爲應用程序分配一塊新內存頁,賦值頁面內容,最後讓應用程序寫入剛分配的新內存頁。


使用內存映射文件

三個步驟:
1、創建或者打開一個文件的內核對象,該對象標識了我們想要用作爲內存映射文件的那個磁盤文件
2、創建一個文件映射內核對象,來告訴系統文件的大小以及我們打算如何訪問文件
3、告訴系統把文件映射對象的部分或全部映射到進程的地址空間中。


創建或者打開文件內核對象

CreateFile()函數創建或者打開一個文件內核對象。返回文件內核對象的句柄。

調用CreateFile()是爲了告訴操作系統文件映射的物理存儲器所在位置。傳入的路徑是文件在磁盤上所在的位置,文件映射對象的物理存儲器來自該文件。

創建文件映射內核對象

調用CreateFileMapping()告訴系統文件映射對象需要多大的物理存儲器。指定訪問屬性。

int main()
{
    HANDLE hFile = CreateFile(TEXT("MMFTest.Dat"), GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    //文件大小爲0byte

    HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 100, NULL);

    //文件大小變爲100byte

    CloseHandle(hFile);
    CloseHandle(hFileMap);

    return 0;
}

將文件的數據映射到進程的地址空間

創建了文件映射對象後,需要爲文件的數據預訂一塊地址空間並將磁盤文件數據作爲物理存儲器撥給地址空間區域。完成映射。

MapViewOfFile()函數。
MapViewOfFileEx()函數可以把文件映射到指定的基地址。

當把一個文件映射到進程的地址空間中時,不必一下子映射整個文件,可以每次只把文件的一小部分映射到地址空間中,文件中被映射到進程地址空間的部分被稱爲視圖,其名稱MapViewOfFile()來源於此。

把一個文件的一個視圖映射到進程的地址空間中,需要告訴操作系統兩件事情:

1、把數據文件的哪個字節映射到視圖的第一個字節(起點)
2、把數據文件的多少映射到地址空間中去(大小)

若指定FILE_MAP_COPY,則類似寫時複製機制,不會修改原始數據文件。


從進程地址空間撤銷對文件數據的映射

UnmapViewOfFile(),撤銷映射,釋放預訂的地址空間區域。

如果需要將緩存中的修改寫入磁盤,需要調用FlushViewOfFile()。


由於可以只映射文件的一個視圖,而這個視圖只是文件的一個小部分數據,故完成對文件第一個視圖訪問後,撤銷對文件這一部分的映射,然後把另一部分映射到視圖中。可以處理超大文件。

系統允許把同一個文件中的數據映射到多個視圖中,如果把多個進程把同一數據文件映射到多個視圖中,數據會保持一致,因爲數據文件中的每個頁面在內存中只有一份,但被映射到了多個進程的地址空間中。

第一個進程調用MapViewOfFile()返回的內存地址,與第二個進程調用MapViewOfFile()返回的內存地址很可能不相同。即使兩個進程都映射同一個文件映射對象的視圖。


用內存映射文件在進程間共享數據

共享機制:多個進程映射同一個文件映射對象的視圖。注意,對多個進程共享同一個文件映射對象來說,所有進程使用的文件映射對象的名稱必須完全相同。

同所有內核對象一樣,可以通過:繼承,命名,複製三種方式跨進程共享對象。


共享內存

以頁交換文件爲後備存儲器的內存映射文件

創建一個內存映射文件相當於先預訂一塊地址空間區域,然後再給區域調撥物理存儲器。唯一的不同在於內存映射文件的物理存儲器是系統的頁交換文件中分配(內存)。

不需要調用CreateFile,只需要調用CreateFileMapping()函數,將INVALID_HANDLE_VALUE作爲hFile參數傳入。告訴操作系統我們創建的是內存映射,不是磁盤文件映射。

  • 例:

寫一個創建共享內存,並寫入數據

    char* pData = NULL;

    HANDLE  hFileMap = NULL;
    hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, _T("WndData"));
    if (!hFileMap)  // 不存在則創建  
    {
        hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1024, _T("WndData"));
    }

    if (hFileMap != NULL)
    {
        pData = (char*)MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0);  

        if (pData == NULL)
        {
            CloseHandle(hFileMap);
            hFileMap = NULL;
        }
    }

    HANDLE hMutex = CreateMutex(NULL, TRUE, _T("WndMutex"));

    char* strValue = "123abcpStr";
    //pData = strValue;  
    memcpy(pData, strValue, strlen(strValue));

    FlushViewOfFile(pData, sizeof(HWND*));  

    ReleaseMutex(hMutex);

讀取共享數據:

 HANDLE hMutex = NULL;
    while (true)
    {
        hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, _T("WndMutex"));
        if (NULL != hMutex)
        {
            break;
        }
    }

    WaitForSingleObject(hMutex, INFINITE);

    HANDLE hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, _T("WndData"));

    char* pData = (char*)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 1024);
    char* strTemp = pData;
    cout<<strTemp;


    UnmapViewOfFile(pData);

    ReleaseMutex(hMutex);

虛擬地址如何轉化爲物理地址

操作系統爲每個進程維持了一個頁表,即獨立的虛擬地址空間。

頁表會將虛擬地址空間頁映射到物理頁。每次地址翻譯硬件硬件一個地址轉化爲物理地址時都會讀取頁表。

頁表中每個條目由一個有效位和地址字段組成。有效位表明了該物理頁是否被緩存在內存中。

如果設置了有效位,那麼地址字段就指向內存中相應的物理頁起始位置。這個內存物理頁中緩存了該虛擬頁。

如果沒有設置有效位,若是一個空地址則表明這個虛擬頁還未被分配,否則,這個地址就指向虛擬頁在磁盤的起始位置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章