原文:
記憶體對應檔(Mapping File)是Windows記憶體管理中的重要一環,也是編程
技術中比較進階的一個話題。目前關於這方面的資料比較少,而其實記憶體映射
檔案其實對我們的對於Windows的記憶體瞭解很重要,在這裡把筆者的心得寫
出來,和大家一起討論。
記憶體空間及映射
相信大家都已經知道,在WIN32中和16位Windows的最大不同就是WIN32
引入了面向進程的獨立虛擬位址,這個地址的定址空間達到了4GB(2^32),當然
這個地址是虛擬。每個進程擁有自己的獨立空間,進程A的地址0X10000000
和進程B的地址0X10000000沒有絲毫的聯絡(只是在使用者進程地址空間,不包括其他
範圍)。說到這個地方可能大家會奇怪了,我的機器中只有64M(或者128M等)記憶體呀,怎麼會有這麼大的地址空間呢?而進程A和進程B的同樣的地址又會如何識別使得不衝突呢?
這裡先讓我們來看看Windows的記憶體空間(註:這裡我們都以Win9X來討論,
當然Win2K或者WinNT和9X在某些方面會不大一樣)
0x00000000----0x003FFFFF 4M 屬於系統保留地區
0x00400000---0x7FFFFFFF 2G-4M 面向進程獨立的地址空間
0x80000000--0xBFFFFFFF 1G Win32共用的空間,用來存放
記憶體對應檔等
0xC0000000---0xFFFFFFFF 1G 用來存放Vxd等
有上面的列表可知,使用者的程式運行在第二個位址範圍中,而我們用來討論的對應檔則放在了第三個位址範圍中.而我們偵錯工具的時候經常有看到某個指標變數的值
為多少,這個值就指的是虛擬位址空間中的地址.
那麼Windows是如何將這個虛擬位址空間轉化為實際的PC上的RAM的地址呢?
這就牽涉到映射的問題,也就是以頁(page)為基本單位實現兩個地址的對應.這個相信
在作業系統這門課裡已經學習過,這裡就不再重複了.在上面這個問題中,地址情況
可能如下:
進程A RAM 進程B
這樣,相信大家都已經清楚了程式訪問中的地址空間和具體的OS訪問的地址之間的關係了吧(關於頁的大小,不同的平台有不同的值,Windows的是4K,我們可以用GetSystemInfo這個API返回的SYSTEM_INFO中的dwPageSize得知道)
(備忘2:實際的映射過程中,是以64K為邊界對齊的)
虛擬記憶體
然而,我們知道RAM是寶貴而稀少的,早在16位的Windows時代已經推出
了硬碟分頁檔以提供虛擬記憶體,Win32則是提供了硬碟上的分頁檔來繼續
支援虛擬記憶體.根據Richter的說法:”系統頁面的大小是決定應用程式能使用多少
實體記憶體的最重要的因素,RAM只是很小的影響”.在實際的記憶體訪問過程中,系統
先會上RAM中尋找需要的資料,如果找不到,就會提供一個分頁錯誤讓OS上頁面
檔案上去找,如果找到則把分頁檔的內容載入到RAM繼續訪問,否則就報錯
提示”無效的分頁錯誤”(也是我們最常碰到的程式錯誤).在這裡,我們不妨把頁面
檔案理解為”後備的RAM”.(Windows提供給使用者控制虛擬記憶體的方法是在
控制台中的系統選項).所以在這種情況下,RAM的主要作用只是起到了
和硬碟上的分頁檔做資料的交換,所以才有了Richter的說法.
如果使用者程式要自己使用虛擬記憶體,那麼首先的第一步是在進程地址
空間中保留(rerserve)一塊地址(在4M—2G中),然後再把這塊空間提交(commit)
給真正的記憶體.Windows提供給我們的對虛擬記憶體的操作介面是VirtualAlloc和
VirtualFree這一組API,這樣我們就可以利用虛擬記憶體的龐大的特性來處理
一般程式難以解決的問題.比如假設有個二維結構Item[300][256];裡面每個單位
為200個位元組, 現在要修改裡面的某個單元,要實際的分配這麼大的記憶體幾乎是
不可能的,而分匹處理又難以體現二維結構直接用下標訪問的簡潔性,這樣我們
就可以先保留下這個龐大的結構,然後只提交要修改的部分給實際的記憶體,使得
最後的操作簡潔而有效.
Windows提供的保留和提交虛擬記憶體的函數只是一個:VirtualAlloc,不過是裡面
的參數不一樣,拿上面的例子而言,我們可以這樣處理:
LPVOID pStart = NULL;
LPVOID pItem = NULL;
DWORD Offset=0;
// 在系統預設位置保留整個資料結構,返回保留的首地址
pStart =VirtualAlloc(NULL,300*256*200, MEM_RESERVE,PAGE_READWRITE);
…
// 計算出要存取的位移位置
….
// 只提交其中的一部分給記憶體
pItem =VirtualAlloc(pStart+Offset,200,MEM_RESERVE,PAGE_READWRITE);
// 直接修改
pItem=….
VirtualFree(pStart, 300*256*200, PAGE_READWRITE);
可是虛擬記憶體也會帶來不方便的地方,假設我們要運行一個程式,按照前面
的做法是要使用到分頁檔來作為虛擬記憶體而訪問,這樣系統必然是先保留
程式的地址空間,然後提交實體記憶體,接下來把資料和代碼從硬碟上的程式檔案
拷貝到系統的分頁檔,最後載入運行.這樣的結果必然是使得載入一個應用
程式的時間變的很長,所以系統真正的做法是把程式檔案(.exe)直接當作是記憶體
檔案而使用,這樣就不再從分頁檔中分配空間使得載入的時間大大增加.
這個特性無疑是非常誘人的,居然可以直接拿檔案當記憶體,那不是很方便嗎?
是的.在系統載入exe檔案和dll檔案的時候,系統是自動這麼處理的.那麼如果
是一般的資料檔案要使用這種特性可以嗎?答案是肯定的,也就是我們繞了一個
大圈子最終要說的今天的話題:記憶體對應檔.