【windows核心編程】第十七章 內存映射文件之一

 

1 映射到內存的可執行文件和DLLwindows

系統預約一塊足夠大的地址空間來容納.exe文件,待預訂的地址空間區域的具體位置已經在PE文件中(這裏是exe)中指定了,默認狀況下,.exe文件的基地址是0x00400000(對運行在64位windows下的64位程序來講,這個地址可能會有所不一樣。)可是,只須要在構建exe時使用/BASE連接器開關就能夠爲本身的應用程序指定一個不一樣的地址。app

系統會對地址空間區域進行標註,代表該區域的後備物理地址存儲器來自磁盤上的exe文件而非來自系統的頁交換文件。函數

 

默認狀況下,MS的鏈接器將X86平臺的DLL的基地址設爲0x10000000,將X64平臺的DLL的基地址設爲0x00400000,可是隻要在構建DLL時用/BASE連接器開關就能夠指定一個不一樣的基地址,全部與windows一塊兒發佈的系統DLL都有不一樣的基地址,這樣即便把他們載入同一個地址空間也不會發生重疊。ui

若是系統沒法在DLL文件指定的基地址處預約區域,緣由多是:一、該區域已經被其餘exe或DLL佔用, 二、區域不夠大。 這個時候系統會嘗試在另外一個地址來爲DLL預約地址空間區域。若是系統沒法將DLL載入指定的基地址,那麼緣由:DLL中不包含重定位信息(當使用/FIXED連接器開關來構建DLL時就去除了重定位信息)。spa

重定位不只須要用頁交換文件中額外的存儲空間,並且會增長載入DLL所需的時間!線程

系統會對地址空間區域進行標註,代表該區域的後備物理存儲器來自磁盤上的DLL文件而非來自系統的頁交換文件,另外若是windows不能將DLL加載到指定的基地址而必須重定位的話,那麼系統還會另外進行標註,代表DLL中有一部分物理存儲器被映射到了頁交換文件。調試

 

 

 

 2 默認狀況下同一個可執行文件或DLL的多個實例不會共享靜態數據code

  也就是說,一個可執行文件或DLL裏的全局變量,對於多個該可執行文件或DLL的實例來講,他們之間的這些靜態變量是相互獨立的。對象

若是一個應用程序正在運行,那麼當咱們爲這個打應用程序建立一個新的進程時,系統只不過是打開另外一個內存映射視圖(memory-mapped-view),系統建立一個新的進程對象,並(爲主線程)建立一個新的線程對象。這個新打開的內存映射視圖隸屬一個文件映射對象(file-mapping object),後者用來標識可執行文件的映像。系統同時給進程對象和線程對象分別指定新的進程ID(process id)和新的線程ID(thread id)。    經過內存映射文件,同一個應用程序的多個實例之間能夠共享內存中的代碼和數據。blog

   2.1關於數據

在exe中,數據位於代碼以後, 應用程序的第二個實例開始運行時,系統只不過是把包含應用程序代碼和數據的虛擬內存頁面映射到第二個實例的地址空間中。

文件的內容被分爲段,代碼在一個段中,全局變量在一個段中,段是對齊到頁面大小的整數倍。 應用程序能夠經過調用GetSystemInfo來檢測頁面大小。 在exe或DLL中,代碼段一般位於數據段前面。

若是應用程序的一個實例修改了數據頁面的一些全局變量,那麼應用程序的其餘實例的內存都會被修改,爲了不這種狀況,系統經過內存管理系統的【寫時複製】(copy-on-write)來防止這種狀況發生。  若是實例A要修改數據頁面P2上的一個全局變量g,那麼系統會從新申請一個頁面newP,而後複製P2上的內容,最後讓A寫大新頁面newP,這樣其餘實例的內存都不會受到影響。

  2.2關於代碼

 除了數據,還有代碼,一種狀況是咱們用VS調試的時候,在源碼某個地方下斷點,那麼下斷點的地方源碼就被修改了,由於調試器會把一條彙編語言指令改爲一條激活調試器的指令。假設此時已經有該程序的其餘實例,此時一樣會按照寫時複製的機制來去除這種影響,系統複製一個新的代碼頁,把原始頁面複製到新頁面,而後在新頁面上修改源代碼。

 

3 如何讓同一個可執行文件或DLL的多個實例共享靜態數據

 補充知識:每一個exe或DLL文件映像由許多段組成,按照慣例,每一個標準的段名都以點號開始,例如,代碼段放在一個名叫.text的段中,未經初始化的數據放在.bss段中,已經初始化的數據放在.data段中。 每一個段都由一些與之相關聯的屬性,以下:

READ         能夠從該段讀取數據

WRITE       能夠向該段寫入數據

EXECUTE   能夠執行該段的內容

SHARED     該段的內容爲多個實例所共享(這個屬性事實上關閉了寫時複製機制)

 

使用VS提供的命令,dumpbin能夠查看各個段, dumpbin /headers XXX.exe 回車便可

 

下面舉個例子,模仿飛秋的只能啓動一個實例的功能,以下:

在某個合適的地方,例如XX.CPP文件的頭部

1 #pragma data_seg("cuihaoshared")  //注意這個段名,區分大小寫,在用dumpbin查看的時候,發現該段名爲截取爲"cuihaosh",猜想可能對長度有限制
2 bool g_bOnlyOneInstance = false;      //必須初始化,不然不能被放入cuihaoshared段,而被放入未初始化段
3 #pragma data_seg()        //結束
4 
5 #pragma comment(linker, "/section:cuihaoshared,rws") //段名區分大小寫,讀/寫/共享

 

在XX.CPP所在類的析構函數中

1 g_bOnlyOneInstance = false;  //這句話寫在析構函數中 

 

 

 

在應用程序類構造函數中 

1 if (true == g_bOnlyOneInstance) 2 { 3    AfxMessageBox(_T("only one instance"));  //提示一下
4    TerminateProcess(::GetCurrentProcess(), 1);   //結束本進程
5 }

 

 

 

 上面提到,若是#pragma data_seg("name") 和 #pragma data_seg()之間的全局變量不初始化的時候不會放入name段,而是會放入未初始化的段, 有一個寫法能夠不初始化也放入本身新建的段,前提是MS VC++的編譯器才支持, 使用allocate聲明符,以下:

1 #pragma data_seg("name")
2 int a = 0;  //放入name段
3 int b;        //不放入name段
4 #pragma data_seg()
5 
6 __declspec(allocate("name")) int c = 2;  //放入name段
7 
8 __declspec(allocate("name")) int d;     //放入name段

 

 



that's all, thank you!

相關文章
相關標籤/搜索