windows核心程式設計--記憶體對映檔案

Mobidogs發表於2020-04-04

與虛擬記憶體一樣,記憶體對映檔案可以用來保留一個地址空間的區域,並將物理儲存器提交給該區域。它們之間的差別是,物理儲存器來自一個已經位於磁碟上的檔案,而不是系統的頁檔案。一旦該檔案被對映,就可以訪問它,就像整個檔案已經載入記憶體一樣。

記憶體對映檔案可以用於3個不同的目的:

• 系統使用記憶體對映檔案,以便載入和執行. e x e和D L L檔案。這可以大大節省頁檔案空間和應用程式啟動執行所需的時間。

• 可以使用記憶體對映檔案來訪問磁碟上的資料檔案。這使你可以不必對檔案執行I / O操作,並且可以不必對檔案內容進行快取。

• 可以使用記憶體對映檔案,使同一臺計算機上執行的多個程式能夠相互之間共享資料。Wi n d o w s確實提供了其他一些方法,以便在程式之間進行資料通訊,但是這些方法都是使用記憶體對映檔案來實現的,這使得記憶體對映檔案成為單個計算機上的多個程式互相進行通訊的最有效的方法。

記憶體對映的可執行檔案和DLL檔案

當執行緒呼叫C r e a t e P r o c e s s時,系統將執行下列操作步驟:

1) 系統找出在呼叫C r e a t e P r o c e s s時設定的. e x e檔案。如果找不到這個. e x e檔案,程式將無法建立,C r e a t e P r o c e s s將返回FA L S E。

2) 系統建立一個新程式核心物件。

3) 系統為這個新程式建立一個私有地址空間。

4) 系統保留一個足夠大的地址空間區域,用於存放該. e x e檔案。該區域需要的位置在. e x e檔案本身中設定。按照預設設定, . e x e檔案的基地址是0 x 0 0 4 0 0 0 0 0(這個地址可能不同於在6 4位Windows 2000上執行的6 4位應用程式的地址),但是,可以在建立應用程式的. e x e檔案時過載這個地址,方法是在連結應用程式時使用連結程式的/ B A S E選項。

5) 系統注意到支援已保留區域的物理儲存器是在磁碟上的. e x e檔案中,而不是在系統的頁檔案中。

當. e x e檔案被對映到程式的地址空間中之後,系統將訪問. e x e檔案的一個部分,該部分列出了包含. e x e檔案中的程式碼要呼叫的函式的D L L檔案。然後,系統為每個D L L檔案呼叫L o a d L i b r a r y函式,如果任何一個D L L需要更多的D L L,那麼系統將呼叫L o a d L i b r a r y函式,以便載入這些D L L。每當呼叫L o a d L i b r a r y來載入一個D L L時,系統將執行下列操作步驟,它們均類似上面的第4和第5個步驟:

1) 系統保留一個足夠大的地址空間區域,用於存放該D L L檔案。該區域需要的位置在D L L檔案本身中設定。按照預設設定, M i c r o s o f t的Visual C++ 建立的D L L檔案基地址是0 x 1 0 0 0 0 0 0 0(這個地址可能不同於在6 4位Windows 2000上執行的6 4位D L L的地址)但是,你可以在建立D L L檔案時過載這個地址,方法是使用連結程式的/ B A S E選項。Wi n d o w s提供的所有標準系統D L L都擁有不同的基地址,這樣,如果載入到單個地址空間,它們就不會重疊。

2) 如果系統無法在該D L L的首選基地址上保留一個區域,其原因可能是該區域已經被另一個D L L或. e x e佔用,也可能是因為該區域不夠大,此時系統將設法尋找另一個地址空間的區域來保留該D L L。

3) 系統會注意到支援已保留區域的物理儲存器位於磁碟上的D L L檔案中,而不是在系統的頁檔案中。

如果由於某個原因系統無法對映. e x e和所有必要的D L L檔案,那麼系統就會向使用者顯示一個訊息框,並且釋放程式的地址空間和程式物件。
當所有的. e x e和D L L檔案都被對映到程式的地址空間之後,系統就可以開始執行. e x e檔案的啟動程式碼。當. e x e檔案被對映後,系統將負責所有的分頁、緩衝和快取記憶體的處理。
在可執行檔案或DLL的多個例項之間共享靜態資料 (通過定義共享的節)

 

 

 

全域性資料和靜態資料不能被同一個. e x e或D L L檔案的多個映像共享,這是個安全的預設設定。但是,在某些情況下,讓一個. e x e檔案的多個映像共享一個變數的例項是非常有用和方便的。例如,Wi n d o w s沒有提供任何簡便的方法來確定使用者是否在執行應用程式的多個例項。但是,如果能夠讓所有例項共享單個全域性變數,那麼這個全域性變數就能夠反映正在執行的例項的數量。
 記憶體對映資料檔案

作業系統使得記憶體能夠將一個資料檔案對映到程式的地址空間中。因此,對大量的資料進行操作是非常方便的。

為了理解用這種方法來使用記憶體對映檔案的功能,讓我們看一看如何用4種方法來實現一個程式,以便將檔案中的所有位元組的順序進行倒序。

方法1:一個檔案,一個快取

第一種方法也是理論上最簡單的方法,它需要分配足夠大的記憶體塊來存放整個檔案。該檔案被開啟,它的內容被讀入記憶體塊,然後該檔案被關閉。檔案內容進入記憶體後,我們就可以對所有位元組的順序進行倒序,方法是將第一個位元組倒騰為最後一個位元組,第二個位元組倒騰為倒數第二個位元組,依次類推。這個倒騰操作將一直進行下去直到檔案的中間位置。當所有的位元組都已經倒騰之後,就可以重新開啟該檔案,並用記憶體塊的內容來改寫它的內容。

這種方法實現起來非常容易,但是它有兩個缺點。首先,必須分配一個與檔案大小相同的記憶體塊。如果檔案比較小,那麼這沒有什麼問題。但是如果檔案非常大,比如說有2 G B大,那該怎麼辦呢?一個3 2位的系統不允許應用程式提交那麼大的實體記憶體塊。因此大檔案需要使用不同的方法。

第二,如果程式在執行過程的中間被中斷,也就是說當倒序後的位元組被重新寫入該檔案時程式被中斷,那麼檔案的內容就會遭到破壞。防止出現這種情況的最簡單的方法是在對它的內容進行倒序之前先製作一個原始檔案的拷貝。如果整個程式執行成功,那麼可以刪除該檔案的拷貝。這種方法需要更多的磁碟空間。

 方法2:兩個檔案,一個快取

在第二種方法中,你開啟現有的檔案,並且在磁碟上建立一個長度為0的新檔案。然後分配一個比較小的內部快取,比如說8 KB。你找到離原始檔案結尾還有8 KB的位置,將這最後的8 KB讀入快取,將位元組倒序,再將快取中的內容寫入新建立的檔案。這個尋找、讀入、倒序和寫入的操作過程要反覆進行,直到到達原始檔案的開頭。如果檔案的長度不是8 KB的倍數,那麼必須進行某些特殊的處理。當原始檔案完全處理完畢之後,將原始檔案和新檔案關閉,並刪除原始檔案。

這種方法實現起來比第一種方法要複雜一些。它對記憶體的使用效率要高得多,因為它只需要分配一個8 KB的快取塊,但是它存在兩個大問題。首先,它的處理速度比第一種方法要慢,原因是在每個迴圈操作過程中,在執行讀入操作之前,必須對原始檔案進行尋找操作。第二,這種方法可能要使用大量的硬碟空間。如果原始檔案是400 MB,那麼隨著程式的不斷執行,新檔案就會增大為400 MB。在原始檔案被刪除之前,兩個檔案總共需要佔用800 MB的磁碟空間。這比應該需要的空間大400 MB。由於存在這個缺點,因此引來了下一個方法。

 方法3:一個檔案,兩個快取

如果使用這個方法,那麼我們假設程式初始化時分配了兩個獨立的8 KB快取。程式將檔案的第一個8 KB讀入一個快取,再將檔案的第二個8 KB 讀入另一個快取。然後程式將兩個快取的內容進行倒序,並將第一個快取的內容寫回檔案的結尾處,將第二個快取的內容寫回同一個檔案的開始處。每個迭代操作不斷進行(以8 KB為單位,從檔案的開始和結尾處移動檔案塊)。如果檔案的長度不是16 KB的倍數,並且有兩個8 KB的檔案塊相重疊,那麼就需要進行一些特殊的處理。這種特殊處理比上一種方法中的特殊處理更加複雜,不過這難不倒經驗豐富的程式設計員。

與前面的兩種方法相比,這種方法在節省硬碟空間方面有它的優點。由於所有內容都是從同一個檔案讀取並寫入同一個檔案,因此不需要增加額外的磁碟空間,至於記憶體的使用,這種方法也不錯,它只需要使用16 KB的記憶體。當然,這種方法也許是最難實現的方法。與第一種方法一樣,如果程式被中斷,本方法會導致資料檔案被破壞。

下面讓我們來看一看如何使用記憶體對映檔案來完成這個過程。

方法4:一個檔案,零快取

當使用記憶體對映檔案對檔案內容進行倒序時,你開啟該檔案,然後告訴系統將虛擬地址空間的一個區域進行倒序。你告訴系統將檔案的第一個位元組對映到該保留區域的第一個位元組。然後可以訪問該虛擬記憶體的區域,就像它包含了這個檔案一樣。實際上,如果在檔案的結尾處有一個單個0位元組,那麼只需要呼叫C執行期函式_ s t r r e v,就可以對檔案中的資料進行倒序操作。

這種方法的最大優點是,系統能夠為你管理所有的檔案快取操作。不必分配任何記憶體,或者將檔案資料載入到記憶體,也不必將資料重新寫入該檔案,或者釋放任何記憶體塊。但是,記憶體對映檔案仍然可能出現因為電源故障之類的程式中斷而造成資料被破壞的問題。


 使用記憶體對映檔案

若要使用記憶體對映檔案,必須執行下列操作步驟:

1) 建立或開啟一個檔案核心物件,該物件用於標識磁碟上你想用作記憶體對映檔案的檔案。

2) 建立一個檔案對映核心物件,告訴系統該檔案的大小和你打算如何訪問該檔案。

3) 讓系統將檔案對映物件的全部或一部分對映到你的程式地址空間中。

當完成對記憶體對映檔案的使用時,必須執行下面這些步驟將它清除:

1) 告訴系統從你的程式的地址空間中撤消檔案對映核心物件的映像。

2) 關閉檔案對映核心物件。

3) 關閉檔案核心物件。

步驟1:建立或開啟檔案核心物件

若要建立或開啟一個檔案核心物件,總是要呼叫C r e a t e F i l e函式:

 


步驟2:建立一個檔案對映核心物件

呼叫C r e a t e F i l e函式,就可以將檔案映像的物理儲存器的位置告訴作業系統。你傳遞的路徑名用於指明支援檔案映像的物理儲存器在磁碟(或網路或光碟)上的確切位置。這時,必須告訴系統,檔案對映物件需要多少物理儲存器。若要進行這項操作,可以呼叫C r e a t e F i l e M a p p i n g函式:

 


步驟3:將檔案資料對映到程式的地址空間

當建立了一個檔案對映物件後,仍然必須讓系統為檔案的資料保留一個地址空間區域,並將檔案的資料作為對映到該區域的物理儲存器進行提交。可以通過呼叫M a p Vi e w O f F i l e函式來進行這項操作:

 


步驟4:從程式的地址空間中撤消檔案資料的映像

當不再需要保留對映到你的程式地址空間區域中的檔案資料時,可以通過呼叫下面的函式將它釋放:

 


為了提高速度,系統將檔案的資料頁面進行快取記憶體,並且在對檔案的對映檢視進行操作時不立即更新檔案的磁碟映像。如果需要確保你的更新被寫入磁碟,可以強制系統將修改過的資料的一部分或全部重新寫入磁碟映像中,方法是呼叫F l u s h Vi e w O f F i l e函式:

 


步驟5和步驟6:關閉檔案對映物件和檔案物件

不用說,你總是要關閉你開啟了的核心物件。如果忘記關閉,在你的程式繼續執行時會出現資源洩漏的問題。當然,當你的程式終止執行時,系統會自動關閉你的程式已經開啟但是忘記關閉的任何物件。但是如果你的程式暫時沒有終止執行,你將會積累許多資源控制程式碼。因此你始終都應該編寫清楚而又“正確的”程式碼,以便關閉你已經開啟的任何物件。若要關閉檔案對映物件和檔案物件,只需要兩次呼叫C l o s e H a n d l e函式,每個控制程式碼呼叫一次:

讓我們更加仔細地觀察一下這個程式。下面的虛擬碼顯示了一個記憶體對映檔案的例子:

 


上面的程式碼顯示了對記憶體對映檔案進行操作所用的“預期”方法。但是,它沒有顯示,當你呼叫M a p Vi e w O f F i l e時系統對檔案物件和檔案對映物件的使用計數的遞增情況。這個副作用是很大的,因為它意味著我們可以將上面的程式碼段重新編寫成下面的樣子:

 


當對記憶體對映檔案進行操作時,通常要開啟檔案,建立檔案對映物件,然後使用檔案對映物件將檔案的資料檢視對映到程式的地址空間。由於系統遞增了檔案物件和檔案對映物件的內部使用計數,因此可以在你的程式碼開始執行時關閉這些物件,以消除資源洩漏的可能性。

如果用同一個檔案來建立更多的檔案對映物件,或者對映同一個檔案對映物件的多個檢視,那麼就不能較早地呼叫C l o s e H a n d l e函式——以後你可能還需要使用它們的控制程式碼,以便分別對C r e a t e F i l e M a p p i n g和M a p Vi e w O f F i l e函式進行更多的呼叫。

使用記憶體對映檔案來處理大檔案
使用記憶體對映檔案在程式之間共享資料

Wi n d o w s總是出色地提供各種機制,使應用程式能夠迅速而方便地共享資料和資訊。這些機制包括R P C、C O M、O L E、D D E、視窗訊息(尤其是W M _ C O P Y D ATA)、剪貼簿、郵箱、管道和套接字等。在Wi n d o w s中,在單個計算機上共享資料的最低層機制是記憶體對映檔案。不錯,如果互相進行通訊的所有程式都在同一臺計算機上的話,上面提到的所有機制均使用記憶體對映檔案從事它們的煩瑣工作。如果要求達到較高的效能和較小的開銷,記憶體對映檔案是舉手可得的最佳機制。

資料共享方法是通過讓兩個或多個程式對映同一個檔案對映物件的檢視來實現的,這意味著它們將共享物理儲存器的同一個頁面。因此,當一個程式將資料寫入一個共享檔案對映物件的檢視時,其他程式可以立即看到它們檢視中的資料變更情況。注意,如果多個程式共享單個檔案對映物件,那麼所有程式必須使用相同的名字來表示該檔案對映物件。

讓我們觀察一個例子,啟動一個應用程式。當一個應用程式啟動時,系統呼叫C r e a t e F i l e函式,開啟磁碟上的. e x e檔案。然後系統呼叫C r e a t e F i l e M a p p i n g函式,建立一個檔案對映物件。最後,系統代表新建立的程式呼叫M a p Vi e w O f F i l e E x函式(它帶有S E C _ I M A G E標誌),這樣, . e x e檔案就可以對映到程式的地址空間。這裡呼叫的是M a p Vi e w O f F i l e E x,而不是M a p Vi e w O f F i l e,這樣,檔案的映像將被對映到存放在. e x e檔案映像中的基地址中。系統建立該程式的主執行緒,將該對映檢視的可執行程式碼的第一個位元組的地址放入執行緒的指令指標,然後C P U啟動該程式碼的執行。

如果使用者執行同一個應用程式的第二個例項,系統就認為規定的. e x e檔案已經存在一個檔案對映物件,因此不會建立新的檔案物件或者檔案對映物件。相反,系統將第二次對映該檔案的一個檢視,這次是在新建立的程式的地址空間環境中對映的。系統所做的工作是將相同的檔案同時對映到兩個地址空間。顯然,這是對記憶體的更有效的使用,因為兩個程式將共享包含正在執行的這部分程式碼的物理儲存器的同一個頁面。

與所有核心物件一樣,可以使用3種方法與多個程式共享物件,這3種方法是控制程式碼繼承性、控制程式碼命名和控制程式碼複製。
 記憶體對映檔案與資料檢視的相關性

附錄:

系統允許你對映一個檔案的相同資料的多個檢視。例如,你可以將檔案開頭的10 KB對映到一個檢視,然後將同一個檔案的頭4 KB對映到另一個檢視。只要你是對映相同的檔案對映物件,系統就會確保對映的檢視資料的相關性。例如,如果你的應用程式改變了一個檢視中的檔案內容,那麼所有其他檢視均被更新以反映這個變化。這是因為儘管頁面多次被對映到程式的虛擬地址空間,但是系統只將資料放在單個R A M頁面上。如果多個程式對映單個資料檔案的檢視,那麼資料仍然是相關的,因為在資料檔案中,每個R A M頁面只有一個例項——正是這個R A M頁面被對映到多個程式的地址空間。

注意Wi n d o w s允許建立若干個由單個資料檔案支援的檔案對映物件。Wi n d o w s不能保證這些不同的檔案對映物件的檢視具有相關性。它只能保證單個檔案對映物件的多個檢視具有相關性。

然而,當對檔案進行操作時,沒有理由使另一個應用程式無法呼叫C r e a t e F i l e函式以開啟由另一個程式對映的同一個檔案。這個新程式可以使用R e a d F i l e和Wr i t e F i l e函式來讀取該檔案的資料和將資料寫入該檔案。當然,每當一個程式呼叫這些函式時,它必須從記憶體緩衝區讀取檔案資料或者將檔案資料寫入記憶體緩衝區。該記憶體緩衝區必須是程式自己建立的一個緩衝區,而不是對映檔案使用的記憶體緩衝區。當兩個應用程式開啟同一個檔案時,問題就可能產生:一個程式可以呼叫R e a d F i l e函式來讀取檔案的一個部分,並修改它的資料,然後使用Wr i t e F i l e函式將資料重新寫入檔案,而第二個程式的檔案對映物件卻不知道第一個程式執行的這些操作。由於這個原因,當你為將被記憶體對映的檔案呼叫C r e a t e F i l e函式時,最好將d w S h a r e M o d e引數的值設定為0。這樣就可以告訴系統,你想要單獨訪問這個檔案,而其他程式都不能開啟它。

只讀檔案不存在相關性問題,因此它們可以作為很好的記憶體對映檔案。記憶體對映檔案決不應該用於共享網路上的可寫入檔案,因為系統無法保證資料檢視的相關性。如果某個人的計算機更新了檔案的內容,其他記憶體中含有原始資料的計算機將不知道它的資訊已經被修改。


 

相關文章