windows核心編程--記憶體對應檔

來源:互聯網
上載者:User

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

記憶體對應檔可以用於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函數:

 

HANDLE CreateFile(   PCSTR pszFileName,   DWORD dwDesiredAccess,   DWORD dwShareMode,   PSECURITY_ATTRIBUTES psa,   DWORD dwCreationDisposition,   DWORD dwFlagsAndAttributes,   HANDLE hTemplateFile);

步驟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函數:

 

HANDLE CreateFileMapping(   HANDLE hFile,   PSECURITY_ATTRIBUTES psa,   DWORD fdwProtect,   DWORD dwMaximumSizeHigh,   DWORD dwMaximumSizeLow,   PCTSTR pszName);

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

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

 

PVOID MapViewOfFile(   HANDLE hFileMappingObject,   DWORD dwDesiredAccess,   DWORD dwFileOffsetHigh,   DWORD dwFileOffsetLow,   SIZE_T dwNumberOfBytesToMap);

步驟4:從進程的地址空間中恢復檔案資料的映像

當不再需要保留映射到你的進程地址空間地區中的檔案資料時,可以通過調用下面的函數將它釋放:

 

BOOL UnmapViewOfFile(PVOID pvBaseAddress);

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

 

BOOL FlushViewOfFile(   PVOID pvAddress,   SIZE_T dwNumberOfBytesToFlush);

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

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

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

 

HANDLE hFile = CreateFile(...);HANDLE hFileMapping = CreateFileMapping(hFile, ...);PVOID pvFile = MapViewOfFile(hFileMapping, ...);// Use the memory-mapped file.UnmapViewOfFile(pvFile);CloseHandle(hFileMapping);CloseHandle(hFile);

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

 

HANDLE hFile = CreateFile(...);HANDLE hFileMapping = CreateFileMapping(hFile, ...);CloseHandle(hFile);PVOID pvFile = MapViewOfFile(hFileMapping, ...);CloseHandle(hFileMapping);// Use the memory-mapped file.UnmapViewOfFile(pvFile);

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

如果用同一個檔案來建立更多的檔案對應物件,或者映射同一個檔案對應物件的多個視圖,那麼就不能較早地調用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。這樣就可以告訴系統,你想要單獨訪問這個檔案,而其他進程都不能開啟它。

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

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.