原文:http://dev.csdn.net/article/14/14321.shtm
前面已經提到:記憶體對應檔是拿檔案直接當作系統的記憶體使用量,那麼它主要
的用途是什麼呢?主要有以下兩點:
1. 直接用記憶體對應檔來訪問磁碟上的資料檔案,無需再進行檔案
的I/0操作.
2. 用來在多個進程之間共用資料.進程間共用資料有很多種方法,比如
發送訊息WM_COPYDATA,匿名管道等等,但他們的低層都毫無例外
的使用到了Mapping File.然而因為WM_COPYDATA一定需要使用
同步函數SendMessage,所以在即時性方面表現的不是很好.
(至於同步和非同步區別可以參考筆者的另一篇文章:
http://www.csdn.net/Develop/read_article.asp?id=14204)
前面已經提到過,記憶體對應檔的位置在3G—4G的空間中,這部分是Win32
所有進程都看的到並且共用的,自然可以用來傳輸資料,另外各個進程所
共用的DLL等也是映射在這個空間範圍.
記憶體對應檔的使用可以分為以下三步:
1.CreateFileMapping 建立一個檔案對應核心對象
2.MapViewOfFile 將檔案資料對應進進程地址空間
3.UnmapViewOfFile 從進程地址空間解除這個映射
下面以Mapping File的兩個主要作用分別給出兩個簡單的例子:
A 直接用記憶體對應檔訪問檔案.
首先在C盤下建立一個Mapping.txt裡面輸入1234567
HANDLE hFile=CreateFile("c:\\mapping.txt",
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
HANDLE hFilemap = CreateFileMapping(hFile..);
NULL,
PAGE_READWRITE,
0,
100, // 只是開闢100個
NULL);
LPVOID pVoid=MapViewOfFile(hFilemap,FILE_MAP_ALL_ACCESS,0,0,0);
Char *Buf=(char *)pVoid;
Buf[0]=”T”
CloseHandle(hFile);
CloseHandle(hFilemap);
UnmapViewOfFile(pVoid);
(注意:沒有考慮異常情況)
這樣,當我們再開啟Mapping.txt的檔案的時候,就發現第一個位元組”1”
已經被改為了’T’.
也許有些讀者會提問:幹嗎這麼麻煩呢?直接用fopen或者CreateFile
不就OK了?是的,小檔案是,可是如果這個檔案有上百兆呢?Mapping
File為我們提供了一種直接映射存取的方便之道.
這裡有個小小的地方要注意,建立映射對象的時候有個保護屬性
fdwProtect可以選擇PAGE_WRITECOPY,顧名思義是用來寫拷貝的,
系統在收到這個參數後,將會從分頁檔中額外的提交實體記憶體
(前面已經提到過,映射對象不使用分頁檔).當發生讀操作的時候,系統
仍舊使用對應檔,當發生寫操作的時候,系統從分頁檔中分配頁面,
從對應檔中拷貝到該頁進行訪問,這樣使得原先的寫操作被丟棄.
讀者可以試著照上面的例子把CreateFileMapping和MapViewOfFile
裡面的兩個對應位元組改為PAGE_WRITECOPY和FILE_MAP_COPY,
這樣原檔案即使有寫操作也不會被改動.
B 在不同的進程間共用資料
要進行共用如果每次都要在硬碟上建立一個檔案該是多麼的麻煩啊,
Windows提供了這樣一種機制:當在建立映射對象的時候如果hFile
填上(HANDLE)0xFFFFFFFF,系統會自動從分頁檔中建立檔案對象.
另外有書上提到共用方式是以p2p的方式還是c/s的架構來進行,
我想不過是開啟的方式不同吧,沒有別的差別,(一個用CreateFileMapping
開啟看是否為已經存在,另一個用OpenFileMapping開啟)
來看個例子;
# define WM_DATACOMING WM_USER+100
進程A:
HANDLE hFilemap=CreateFileMapping((HANDLE)0xFFFFFFFF,
NULL,
PAGE_READWRITE,
0,
100,
"SHARED");
LPVOID pVoid=MapViewOfFile(hFilemap,FILE_MAP_ALL_ACCESS,0,0,0);
memset(pVoid,0,100);
strcpy((char *)pVoid,"this is a mapping file test");
HANDLE hDes=FindWindow(NULL,"MAPPING"); // 對象視窗的名稱
SendMessage(hDes, WM_DATACOMING,0,0);
CloseHandle(hFilemap);
UnmapViewOfFile(pVoid);
進程B(擁有視窗名稱為MAPPING)
// WM_DATACOMING訊息捕捉函數
HANDLE hFilemap=OpenFileMapping(NULL,NULL,"SHARED");
LPVOID pVoid=MapViewOfFile(hFilemap,FILE_MAP_ALL_ACCESS,0,0,0);
Label1->Caption=(char *)pVoid;
可以看到資料已經被正確的傳送過來.
可能有些讀者已經注意到,在這種情況下需要給映射對象取個名字(例子
中為SHARED),是的,在這種用途下需要給它取個名字,而在第一種應用
中這個地方可以被忽略.這裡可能會引起打架的地方就是這個名字了,
如果多個進程建立了多個映射對象,根據名字來不是比較容易衝突了嗎?
是的,這是個問題,筆者建議可以採用表單的名稱(MAPPING)或者別的
唯一的ID來使得不引起混淆.
請注意這個函數:MapViewOfFile,注意到裡面有個單詞:Viewà視
這個函數是把建立好的映射對象真正提交到地址空間去,這就產生了
一個視.Windows中允許映射統一資料檔案的多個視,比如說可以將
一個檔案的全部映射到一個視,然後將他的前10K單獨映射為一個視.
那麼系統是不是真正區別這多個視呢?答案是要看是什麼系統,
如果是Win9x,系統並沒有額外再映射一個新的地址給它,而只是
把原先的基地址加上一個位移量做為新的視的地址而返回,換句話
說地址空間只有一份,而WinNT則是真正的新產生了一個地址空間
返回來.
看看下面這個小例子:
HANDLE hFilemap=CreateFileMapping((HANDLE)0xFFFFFFFF,
NULL,
PAGE_READWRITE,
0,
100,
"SHARED");
// 提交整個地址給空間
LPVOID pVoid1=MapViewOfFile(hFilemap,FILE_MAP_ALL_ACCESS,0,0,0);
// 從位移40產生一個新視
LPVOID pVoid2= MapViewOfFile(hFilemap,FILE_MAP_ALL_ACCESS,0,40,0);
If(pVoid1+40==pVoid2)
MessageBox(“Run On Win95”);
else
MessageBox(“Run On NT”);
可以注意到返回的值為0x8…這符合地址清單中MappingFile的位置.必然在
“Server”中開啟的映射對象的地址和”Client”中利用MapViewOfFile返回的地址
是一致的(9x環境).這也是因為這個部分的地址空間是大家共用的.
那麼既然是一樣的,能不能直接使用這個值呢?比如上面的進程間共用資料
的例子:如果進程A的發送語句改為:
// 把指標值作為參數傳遞
SendMessage(hDes, WM_DATACOMING,(WPARAM) pVoid,0);
進程B的接受訊息部分改為:
LPVOID pVoid=(LPVOID)MSG.WPARAM;
Label1->Caption=(char *)pVoid;
可以看到可以正確的顯示出來,因為指標所指的地方的確是有這麼一筆資料,
那麼是不是意味著我們就能這麼使用呢?答案是否定的,首先這個值相等
只是在Win9x的環境下,在NT環境下是不相等的,另外NT下訪問這個地址
空間的時候要求一定要先使用MapViewOfFile函數.這是第一個原因,更加
重要的是記憶體映射對象屬於核心對象(Kernal Object),這種對象的最大不同就
在於它是系統維護的一塊資料結構,使用者只能通過相應的介面函數進行間接
的訪問.每訪問一次就增加一個引用記數(reference count),當計數器變為0的
時候,系統自動釋放這個核心對象.在上面的例子中,儘管Server端和Client
的值是一樣的,但是如果Server端執行UnmapViewOfFile釋放核心對象的時候,
這部分資料將會被系統釋放掉,因為它的引用計數只是1,只有我們在Client
端使用MapViewOfFile增加這個對象的計數的時候,才不會被系統釋放掉.
堆
Win32的堆位於進程私人空間內,屬於自由分配區,比如大家在C++中常
使用的new操作符就是在這個地方分配的,關於堆的操作有HeapCreate
和HeapAlloc等,這裡就不再繼續討論了.
後記
Mapping File一直是個比較難以討論的問題,在CSDN上也看到不少網友
討論的比較模糊,最後不了了之.筆者對這個問題也一直想搞個明白,在看
Richter的大作<Advanced Windows>的時候,因為記憶體這個部分是連在一起
的好幾章,理論也比較抽象和繁雜,看的很是頭痛.寫出這篇文章也是希望
協助大家更好的理解這個部分,對記憶體有著進一步的瞭解以便更好的開發程式.
有興趣進一步研究者可聯絡 QQ:33854303
xrbeck寫於2002/7/3
參考資料:
1. Windows 進階編程指南 Jeffery Richter
2. Windows程式設計 Charles Petzold
3. Win32多線程程式設計 侯捷譯