Windowsw核心编程 第13章 Windows内存结构
?第1?3章?Wi?n?d?o?w?s的內存結構
13.1?進程的虛擬地址空間
? ? 每個進程都被賦予它自己的虛擬地址空間。對于?3?2位進程來說,這個地址空間是4?G?B,因為3?2位指針可以擁有從0?x?0?0?0?0?0?0?0?0至0?x?F?F?F?F?F?F?F?F之間的任何一個值。這使得一個指針能夠擁有4?294?967?296個值中的一個值,它覆蓋了一個進程的4?G?B虛擬空間的范圍。對于6?4位進程來說,這個地址空間是?1?6?E?B(1?0?1?8?字節(jié)),因為6?4位指針可以擁有從?0?x?0?0?0?0?0?0?0?0?0?0?0?0?0?0?0?0至0?x?F?F?F?F?F?F?F?F?F?F?F?F?F?F?F?F之間的任何值。這使得一個指針可以擁有18?446?744?073?709?551?616個值中的一個值,它覆蓋了一個進程的1?6?E?B虛擬空間的范圍。這是相當大的一個范圍。
? ? 由于每個進程可以接收它自己的私有的地址空間,因此當進程中的一個線程正在運行時,該線程可以訪問只屬于它的進程的內存。屬于所有其他進程的內存則隱藏著,并且不能被正在運行的線程訪問。
? ? 前面說過,每個進程有它自己的私有地址空間。進程?A可能有一個存放在它的地址空間中的數(shù)據(jù)結構,地址是0?x?1?2?3?4?5?6?7?8,而進程B則有一個完全不同的數(shù)據(jù)結構存放在它的地址空間中,地址是0?x?1?2?3?4?5?6?7?8。當進程A中運行的線程訪問地址為0?x?1?2?3?4?5?6?7?8的內存時,這些線程訪問的是進程A的數(shù)據(jù)結構。當進程B中運行的線程訪問地址為?0?x?1?2?3?4?5?6?7?8的內存時,這些線程訪問的是進程B的數(shù)據(jù)結構。進程A中運行的線程不能訪問進程B的地址空間中的數(shù)據(jù)結構。反之亦然。
? ? 當你因為擁有如此大的地址空間可以用于應用程序而興高采烈之前,記住,這是個虛擬地址空間,不是物理地址空間。該地址空間只是內存地址的一個范圍。在你能夠成功地訪問數(shù)據(jù)而不會出現(xiàn)違規(guī)訪問之前,必須賦予物理存儲器,或者將物理存儲器映射到各個部分的地址空間。本章后面將要具體介紹這是如何操作的。
13.2?虛擬地址空間如何分區(qū)
????每個進程的虛擬地址空間都要劃分成各個分區(qū)。地址空間的分區(qū)是根據(jù)操作系統(tǒng)的基本實現(xiàn)方法來進行的。不同的Wi?n?d?o?w?s內核,其分區(qū)也略有不同。表1?3?-?1顯示了每種平臺是如何對進程的地址空間進行分區(qū)的。
? ? 如你所見,3?2位Windows?2000的內核與6?4位Windows?2000的內核擁有大體相同的分區(qū),差別在于分區(qū)的大小和位置有所不同。另一方面,可以看到?Windows?98下的分區(qū)有著很大的不同。下面讓我們看一下系統(tǒng)是如何使用每一個分區(qū)的。
13.2.1?NULL指針分配的分區(qū)?—?適用于Windows?2000和Windows?98
? ? 進程地址空間的這個分區(qū)的設置是為了幫助程序員掌握?N?U?L?L指針的分配情況。如果你的進程中的線程試圖讀取該分區(qū)的地址空間的數(shù)據(jù),或者將數(shù)據(jù)寫入該分區(qū)的地址空間,那么C?P?U就會引發(fā)一個訪問違規(guī)。保護這個分區(qū)是極其有用的,它可以幫助你發(fā)現(xiàn)?N?U?L?L指針的分配情況。
? ? C?/?C?+?+程序中常常不進行嚴格的錯誤檢查。例如,下面這個代碼就沒有進行任何錯誤檢查:
? ? 如果m?a?l?l?o?c不能找到足夠的內存來滿足需要,它就返回?N?U?L?L。但是,該代碼并不檢查這種可能性,它認為地址的分配已經(jīng)取得成功,并且開始訪問?0?x?0?0?0?0?0?0?0?0地址的內存。由于這個分區(qū)的地址空間是禁止進入的,因此就會發(fā)生內存訪問違規(guī)現(xiàn)象,同時該進程將終止運行。這個特性有助于編程員發(fā)現(xiàn)應用程序中的錯誤。
13.2.2?MS-DOS/16位Wi?n?d?o?w?s應用程序兼容分區(qū)?—?僅適用于Windows?98
? ? 進程地址空間的這個4?M?B分區(qū)是Windows?98需要的,目的是維護M?S?-?D?O?S應用程序與1?6位應用程序之間的兼容性。不應該試圖從3?2位應用程序來讀取該分區(qū)的數(shù)據(jù),或者將數(shù)據(jù)寫入該分區(qū)。在理想的情況下,如果進程中的線程訪問該內存,?C?P?U應該產(chǎn)生一個訪問違規(guī),但是由于技術上的原因,M?i?c?r?o?s?o?f?t無法保護這個4?M?B的地址空間。
? ? 在Windows?2000中,1?6位M?S?-?D?O?S與1?6位Wi?n?d?o?w?s應用程序是在它們自己的地址空間中運行的,3?2位應用程序不會對它們產(chǎn)生任何影響。
13.2.3?用戶方式分區(qū)?—?適用于Windows?2000和Windows?98
? ? 這個分區(qū)是進程的私有(非共享)地址空間所在的地方。一個進程不能讀取、寫入、或者以任何方式訪問駐留在該分區(qū)中的另一個進程的數(shù)據(jù)。對于所有應用程序來說,該分區(qū)是維護進程的大部分數(shù)據(jù)的地方。由于每個進程可以得到它自己的私有的、非共享分區(qū),以便存放它的數(shù)據(jù),因此,應用程序不太可能被其他應用程序所破壞,這使得整個系統(tǒng)更加健壯。
? ? Windows?2000?在Windows?2000中,所有的.?e?x?e和D?L?L模塊均加載這個分區(qū)。每個進程可以將這些D?L?L加載到該分區(qū)的不同地址中(不過這種可能性很小)。系統(tǒng)還可以在這個分區(qū)中映射該進程可以訪問的所有內存映射文件。
? ? Windows?98?在Windows?98中,主要的Wi?n?3?2系統(tǒng)D?L?L(K?e?r?n?e?l?3?2?.?d?l?l,A?d?v?A?P?I?3?2?.?d?l?l,U?s?e?r?3?2?.?d?l?l和G?D?I?3?2?.?d?l?l)均加載共享內存映射文件分區(qū)中。.?e?x?e和所有其他D?L?L模塊則加載到這個用戶方式分區(qū)中。所有進程的共享?D?L?L均位于相同的虛擬地址中,但是其他D?L?L可以將這些D?L?L加載到用戶方式分區(qū)的不同地址中(不過這種可能性不大)。另外,在Windows?98中,用戶方式分區(qū)中決不會出現(xiàn)內存映射文件。
13.2.4?64?KB禁止進入的分區(qū)?—?僅適用于Windows?2000
? ? 這個位于用戶方式分區(qū)上面的64?KB分區(qū)是禁止進入的,訪問該分區(qū)中的內存的任何企圖均將導致訪問違規(guī)。M?i?c?r?o?s?o?f?t之所以保留該分區(qū),是因為這樣做將使得M?i?c?r?o?s?o?f?t能夠更加容易地實現(xiàn)操作系統(tǒng)。當將內存塊的地址和它的長度傳遞給Wi?n?d?o?w?s函數(shù)時,該函數(shù)將在執(zhí)行它的操作之前使內存塊生效??梢院苋菀讋?chuàng)建類似下面這個代碼(在3?2位Windows?2000系統(tǒng)上運行):
Win7?X64寫失敗了。
13.2.5?共享的M?M?F分區(qū)?—?僅適用于Windows?98
? ? 這個1?G?B分區(qū)是系統(tǒng)用來存放所有?3?2位進程共享數(shù)據(jù)的地方。例如,系統(tǒng)的動態(tài)鏈接庫K?e?r?n?e?l?3?2?.?d?l?l、A?d?v?A?P?I?3?2?.?d?l?l、U?s?e?r?3?2?.?d?l?l和G?D?I?3?2?.?d?l?l等,全部存放在這個地址空間分區(qū)中,因此,所有3?2位進程都能很容易同時訪問它們。系統(tǒng)還為每個進程將?D?L?L加載相同的內存地址。此外,系統(tǒng)將所有內存映射文件映射到這個分區(qū)中。內存映射文件將在第?1?7章中詳細介紹。
13.2.6?內核方式分區(qū)?—?適用于Windows?2000和Windows?98
????Windows?2000?在6?4位Windows?2000中,4?TB用戶方式分區(qū)看上去與16,?777,?212
TB?的內核方式分區(qū)非常不成比例。并不是內核方式分區(qū)需要使用該虛擬地址空間的
全部空間,它只是說明?6?4位地址空間是非常大的,而該地址空間的大部分是不用的。
系統(tǒng)允許應用程序使用4?TB分區(qū),并且允許內核使用它需要的東西,而內核方式分區(qū)
的大部分是不用的。幸好系統(tǒng)并不需要任何內部數(shù)據(jù)結構來維護內核方式分區(qū)的不用
部分。
????Windows?98?不幸的是,在Windows?98中該分區(qū)中的數(shù)據(jù)是不受保護的。任何應用
程序都可以從該分區(qū)讀取數(shù)據(jù),也可以寫入數(shù)據(jù),因此有可能破壞操作系統(tǒng)。
13.3?地址空間中的區(qū)域
????當進程被創(chuàng)建并被賦予它的地址空間時,該可用地址空間的主體是空閑的,即未分配的。
若要使用該地址空間的各個部分,必須通過調用Vi?r?t?u?a?l?A?l?l?o?c函數(shù)(第1?5章介紹)來分配它里邊的各個區(qū)域。對一個地址空間的區(qū)域進行分配的操作稱為保留?(?r?e?s?e?r?v?i?n?g?)。
????每當你保留地址空間的一個區(qū)域時,系統(tǒng)要確保該區(qū)域從一個分配粒度的邊界開始。對于不同的C?P?U平臺來說,分配粒度是各不相同的。但是,截止到撰寫本書時,所有的?C?P?U平臺(x?8?6、3?2位A?l?p?h?a、6?4位A?l?p?h?a和I?A?-?6?4)都使用6?4?K?B這個相同的分配粒度。
當你保留地址空間的一個區(qū)域時,系統(tǒng)還要確保該區(qū)域的大小是系統(tǒng)的頁面大小的倍數(shù)。頁面是系統(tǒng)在管理內存時使用的一個內存單位。與分配粒度一樣,不同的?C?P?U,其頁面大小也是不同的。x?8?6使用的頁面大小是4?KB,而A?l?p?h?a(當既能運行3?2位Windows?2000也能運行6?4位Windows?2000時)使用的頁面大小則是8?KB。在撰寫本書時,M?i?c?r?o?s?o?f?t預計I?A?-?6?4也使用8K?B的頁面。但是,如果測試顯示使用更大的頁面能夠提高系統(tǒng)的總體性能,那么?M?i?c?r?o?s?o?f?t可以切換到更大的頁面(1?6?K?B或更大)。
注意?有時系統(tǒng)能夠代表你的進程來保留地址空間的區(qū)域。例如,系統(tǒng)可以分配一個地址空間區(qū)域,以便存放進程環(huán)境塊(F?E?B)。F?E?B是由系統(tǒng)創(chuàng)建、操作和撤消的一個小型數(shù)據(jù)結構。當創(chuàng)建一個進程時,系統(tǒng)就為F?E?B分配一個地址空間區(qū)域。
系統(tǒng)也需要創(chuàng)建一個線程環(huán)境塊(?T?E?B),以便管理進程中當前存在的所有線程。用于這些T?E?B的區(qū)域將根據(jù)進程中的線程被創(chuàng)建和撤消等情況而保留和釋放。
雖然系統(tǒng)規(guī)定,要求保留的地址空間區(qū)域均從分配粒度邊界(目前所有平臺上均為6?4?K?B)開始,但是系統(tǒng)本身并不受這個規(guī)定的限制。為你的進程的?P?E?B和T?E?B保留的地址空間區(qū)域很可能不是從?64?KB這個邊界開始的。不過這些保留區(qū)域仍然必須是C?P?U的頁面大小的倍數(shù)。
如果想保留一個10?KB的地址空間區(qū)域,系統(tǒng)將自動對你的請求進行四舍五入,使保留的地址空間區(qū)域的大小是頁面大小的倍數(shù)。這意味著,在?x?8?6平臺上,系統(tǒng)將保留一個1?2?K?B的區(qū)域,在A?l?p?h?a平臺上,系統(tǒng)將保留一個1?6?K?B的區(qū)域。
當你的程序算法不再需要訪問已經(jīng)保留的地址空間區(qū)域時,該區(qū)域應該被釋放。這個過程稱為釋放地址空間的區(qū)域,它是通過調用Vi?r?t?u?a?l?F?r?e?e函數(shù)來完成的。
13.5?物理存儲器與頁文件
? ? 在較老的操作系統(tǒng)中,物理存儲器被視為?計算機擁有的R?A?M的容量。換句話說,如果計算機擁有1?6?M?B的R?A?M,那么加載和運行的應用程序最多可以使用?1?6?M?B的R?A?M。今天的操作系統(tǒng)能夠使得磁盤空間看上去就像內存一樣。磁盤上的文件通常稱為頁文件,它包含了可供所有進程使用的虛擬內存。
? ? 當然,若要使虛擬內存能夠運行,需要得到?C?P?U本身的大量幫助。當一個線程試圖訪問一個字節(jié)的內存時,C?P?U必須知道這個字節(jié)是在R?A?M中還是在磁盤上。
? ? 從應用程序的角度來看,頁文件透明地增加了應用程序能夠使用的?R?A?M(即內存)的數(shù)量。如果計算機擁有?6?4?M?B的R?A?M,同時在硬盤上有一個?100?MB的頁文件,那么運行的應用程序就認為計算機總共擁有1?6?4?M?B的R?A?M。
? ? 當然,實際上并不擁有1?6?4?M?B的R?A?M。相反,操作系統(tǒng)與C?P?U相協(xié)調,共同將R?A?M的各個部分保存到頁文件中,當運行的應用程序需要時,再將頁文件的各個部分重新加載到?R?A?M。由于頁文件增加了應用程序可以使用的?R?A?M的容量,因此頁文件的使用是視情況而定的。如果沒有頁文件,那么系統(tǒng)就認為只有較少的?R?A?M可供應用程序使用。但是,我們鼓勵用戶使用頁文件,這樣他們就能夠運行更多的應用程序,并且這些應用程序能夠對更大的數(shù)據(jù)集進行操作。最好將物理存儲器視為存儲在磁盤驅動器(通常是硬盤驅動器)上的頁文件中的數(shù)據(jù)。這樣,當一個應用程序通過調用Vi?r?t?u?a?l?A?l?l?o?c函數(shù),將物理存儲器提交給地址空間的一個區(qū)域時,地址空間實際上是從硬盤上的一個文件中進行分配的。系統(tǒng)的頁文件的大小是確定有多少物理存儲器可供應用程序使用時應該考慮的最重要的因素,?R?A?M的容量則影響非常小。
? ? 現(xiàn)在,當你的進程中的一個線程試圖訪問進程的地址空間中的一個數(shù)據(jù)塊時,將會發(fā)生兩種情況之一,參見圖1?3?-?2中的流程圖。
? ? 在第一種情況中,線程試圖訪問的數(shù)據(jù)是在?R?A?M中。在這種情況下,?C?P?U將數(shù)據(jù)的虛擬內存地址映射到內存的物理地址中,然后執(zhí)行需要的訪問。
? ? 在第二種情況中,線程試圖訪問的數(shù)據(jù)不在?R?A?M中,而是存放在頁文件中的某個地方。這時,試圖訪問就稱為頁面失效,?C?P?U將把試圖進行的訪問通知操作系統(tǒng)。這時操作系統(tǒng)就尋找R?A?M中的一個內存空頁。如果找不到空頁,系統(tǒng)必須釋放一個空頁。如果一個頁面尚未被修改,系統(tǒng)就可以釋放該頁面。但是,如果系統(tǒng)需要釋放一個已經(jīng)修改的頁面,那么它必須首先將該頁面從R?A?M拷貝到頁交換文件中,然后系統(tǒng)進入該頁文件,找出需要訪問的數(shù)據(jù)塊,并將數(shù)據(jù)加載到空閑的內存頁面。然后,操作系統(tǒng)更新它的用于指明數(shù)據(jù)的虛擬內存地址現(xiàn)在已經(jīng)映射到R?A?M中的相應的物理存儲器地址中的表。這時?C?P?U重新運行生成初始頁面失效的指令,但是這次C?P?U能夠將虛擬內存地址映射到一個物理R?A?M地址,并訪問該數(shù)據(jù)塊。
? ? 系統(tǒng)需要將內存頁面拷貝到頁文件并反過來將頁文件拷貝到內存頁面的次數(shù)越多,你的硬盤倒騰的次數(shù)就越多,系統(tǒng)運行得越慢(倒騰意味著操作系統(tǒng)要花費更多的時間將頁面從內存中轉出轉進,而不是將時間用于程序的運行)。因此,通過給你的計算機增加更多的?R?A?M,就可以減少運行應用程序所需的倒騰次數(shù),這就必然可以大大提高系統(tǒng)的運行速度。所以必須遵循一條基本原則,那就是要讓你的計算機運行得更塊,增加更多的?R?A?M。實際上,在大多數(shù)情況下,若要提高系統(tǒng)的運行性能,增加R?A?M比提高C?P?U的速度所產(chǎn)生的效果更好。
不在頁文件中維護的物理存儲器當閱讀了上一節(jié)后,你必定會認為,如果同時運行許多文件的話,頁文件就可能變得非常大,而且你會認為,每當你運行一個程序時,系統(tǒng)必須為進程的代碼和數(shù)據(jù)保留地址空間的一些區(qū)域,將物理存儲器提交給這些區(qū)域,然后將代碼和數(shù)據(jù)從硬盤上的程序文件拷貝到頁文件中已提交的物理存儲器中。
????實際上系統(tǒng)并不進行上面所說的這些操作。如果它進行這些操作的話,就要花費很長的時間來加載程序并啟動它運行。相反,當啟動一個應用程序的時候,系統(tǒng)將打開該應用程序
的.?e?x?e文件,確定該應用程序的代碼和數(shù)據(jù)的大小。然后系統(tǒng)要保留一個地址空間的區(qū)域,并指明與該區(qū)域相關聯(lián)的物理存儲器是在?.?e?x?e文件本身中。即系統(tǒng)并不是從頁文件中分配地址空間,而是將.?e?x?e文件的實際內容即映像用作程序的保留地址空間區(qū)域。當然,這使應用程序的加載非常迅速,并使頁文件能夠保持得非常小。
? ? 當硬盤上的一個程序的文件映像(這是個?.?e?x?e文件或D?L?L文件)用作地址空間的區(qū)域的物理存儲器時,它稱為內存映射文件。當一個?.?e?x?e文件或D?L?L文件被加載時,系統(tǒng)將自動保留一個地址空間的區(qū)域,并將該文件映像映射到該區(qū)域中。但是,系統(tǒng)也提供了一組函數(shù),使你能夠將數(shù)據(jù)文件映射到一個地址空間的區(qū)域中。關于內存映射文件的詳細說明,將在第?1?7章中介紹。
我的電腦是win7?64?找到桌面我的電腦圖標,右鍵屬性。然后就是下面步驟找到虛擬內存大小設置:
?
????注意?當.?e?x?e或D?L?L文件從軟盤加載時,Windows?98和Windows?2000都能將整個文件從軟盤拷貝到系統(tǒng)的?R?A?M中。此外,系統(tǒng)將從頁文件中分配足夠的內存,以便存放
該文件的映像。如果系統(tǒng)選擇對當前包含該文件的一部分映像的?R?A?M頁面進行裁剪,
那么該內存屬于只能寫入的內存。如果系統(tǒng)?R?A?M上的負載比較小,那么文件始終都
可以直接從R?A?M來運行。
????M?i?c?r?o?s?o?f?t不得不通過軟盤來運行的映射文件,這樣,安裝應用程序才能正確運行。
安裝程序常常從一個軟盤開始,然后用戶將軟盤從驅動器中取出來,再插入另一個軟
盤。如果系統(tǒng)需要回到第一個軟盤,以便加載?.?e?x?e或D?L?L文件的某些代碼,當然該代
碼已經(jīng)不再在軟盤驅動器中了。然而,由于系統(tǒng)將文件拷貝到?R?A?M(并且受頁文件
的支持),要訪問安裝程序是不會有任何問題的。
????系統(tǒng)并不將R?A?M映射文件拷貝在其他可換式介質上,如光盤或網(wǎng)絡驅動器,除非
映射文件是用/?S?WA?P?R?U?N:C?D或/?S?WA?P?R?U?N:N?E?T開關鏈接的。注意,Windows?98
不支持/?S?WA?P?R?U?N映像標志。
13.6?保護屬性
? ? 已經(jīng)分配的物理存儲器的各個頁面可以被賦予不同的保護屬性。表?1?3?-?2顯示了這些保護屬性。
? ? x?8?6和Alpha?CPU不支持“執(zhí)行”保護屬性,不過操作系統(tǒng)軟件卻支持這個屬性。這些?C?P?U將讀訪問視為執(zhí)行訪問。這意味著如果將?PA?G?E?_?E?X?E?C?U?T?E保護屬性賦予內存,那么該內存也將擁有讀優(yōu)先權。當然,不應該依賴這個行為特性,因為在其他?C?P?U上的Wi?n?d?o?w?s實現(xiàn)代碼很可能將“執(zhí)行”保護視為“僅為執(zhí)行”保護。
? ? Windows?98?Windows?98只支持PA?G?E?_?N?O?A?C?C?E?S?S、PA?G?E?_?R?E?A?D?O?N?LY和PA?G?E?_R?E?A?D?W?R?I?T?E等保護屬性。
13.6.1?Copy-On-Write?訪問
? ? 表1?3?-?2列出的保護屬性都是非常容易理解的,不過最后兩個屬性需要作一些說明。一個是PA?G?E?_?W?R?I?T?E?C?O?P?Y,另一個是PA?G?E?_?E?X?E?C?U?T?E?_?W?R?I?T?E?C?O?P?Y。這兩個屬性的作用是為了節(jié)省R?A?M的使用量和頁文件的空間。Wi?n?d?o?w?s支持一種機制,使得兩個或多個進程能夠共享單個內存塊。因此,如果1?0個N?o?t?e?p?a?d實例正在運行,那么所有實例可以共享應用程序的代碼和數(shù)據(jù)頁面。讓所有實例共享同樣的內存頁面將能夠大大提高系統(tǒng)的性能,但是這要求所有實例都將該內存視為只讀或只執(zhí)行的內存。如果一個實例中的線程將數(shù)據(jù)寫入內存修改它,那么其他實例看到的這個內存也將被修改,從而造成一片混亂。
為了防止出現(xiàn)這種混亂,操作系統(tǒng)給共享內存塊賦予了?C?o?p?y?-?O?n?-?Wr?i?t?e保護屬性。當一個.?e?x?e或D?L?L模塊被映射到一個內存地址時,系統(tǒng)將計算有多少頁面是可以寫入的(通常包含代碼的頁面標為PA?G?E?_?E?X?E?C?U?T?E?_?R?E?A?D,而包含數(shù)據(jù)的頁面則標為?PA?G?E?_?R?E?A?D?W?R?I?T?E)。然后,系統(tǒng)從頁文件中分配內存,以適應這些可寫入的頁面的需要。除非該模塊的可寫入頁面是實際的寫入模塊,否則這些頁文件內存是不使用的。
當一個進程中的線程試圖將數(shù)據(jù)寫入一個共享內存塊時,系統(tǒng)就會進行干預,并執(zhí)行下列操作步驟:
1)?系統(tǒng)查找R?A?M中的一個空閑內存頁面。注意,當該模塊初次被映射到進程的地址空間時,該空閑頁面將被頁文件中已分配的頁面之一所映射。當該模塊初次被映射時,由于系統(tǒng)要分配所有可能需要的頁文件,因此這一步不可能運行失敗。
2)?系統(tǒng)將試圖被修改的頁面內容拷貝到第一步中找到的頁面。該空閑頁面將被賦予
PA?G?E?_?R?E?A?D?W?R?I?T?E或PA?G?E?_?E?X?E?C?U?T?E?_?R?E?A?D?W?R?I?T?E保護屬性。原始頁面的保護屬性和數(shù)據(jù)不發(fā)生任何變化。
3)?然后系統(tǒng)更新進程的頁面表,使得被訪問的虛擬地址被轉換成新的?R?A?M頁面。當系統(tǒng)執(zhí)行了這3個操作步驟之后,該進程就可以訪問它自己的內存頁面的私有實例。第1?7章還要詳細地介紹共享內存和C?o?p?y?-?O?n?-?Wr?i?t?e保護屬性。
? ? 此外,當使用?Vi?r?t?u?a?l?A?l?l?o?c函數(shù)來保留地址空間或者提交物理存儲器時,不應該傳遞PA?G?E?_?W?R?I?T?E?C?O?P?Y或PA?G?E?_?E?X?E?C?U?T?E?_?W?R?I?T?E?C?O?P?Y。如果傳遞的話,將會導致Vi?r?t?u?a?l?A?l?l?o?c調用的失敗。對G?e?t?L?a?s?t?E?r?r?o?r的調用將返回E?R?R?O?R?_?I?N?VA?L?I?D?_?PA?R?A?M?E?T?E?R。當操作系統(tǒng)映射.?e?x?e或D?L?L文件映像時,這兩個屬性將被操作系統(tǒng)使用。
Windows?98?Windows?98不支持C?o?p?y?-?O?n?-?Wr?i?t?e保護。當Windows?98發(fā)現(xiàn)需要C?o?p?y?_?O?n?_?Wr?i?t?e保護時,它就立即進行數(shù)據(jù)的拷貝,而不是等待試圖對內存進行寫入操作。
13.6.2?特殊的訪問保護屬性的標志
? ? 除了上面介紹的保護屬性外,還有?3個保護屬性標志,即?PA?G?E?_?N?O?C?A?C?H?E,PA?G?E?_W?R?I?T?E?C?O?M?B?I?N?E和PA?G?E?_?G?U?A?R?D??梢杂?/span>O?R逐位將它們連接,以便將這?3個標志用于任何一個保護屬性(PA?G?E?_?N?O?C?A?C?H?E除外)。
? ? 第一個保護屬性標志PA?G?E?_?N?O?C?A?C?H?E用于停用已提交頁面的高速緩存。一般情況下最好不要使用該標志,因為它主要是供需要處理內存緩沖區(qū)的硬件設備驅動程序的開發(fā)人員使用的。
? ? 第二個保護屬性PA?G?E?_?W?R?I?T?E?C?O?M?B?I?N?E也是供設備驅動程序開發(fā)人員使用的。它允許把單個設備的多次寫入合并在一起,以便提高運行性能。
? ? 最后一個保護屬性標志PA?G?E?_?G?U?A?R?D可以在頁面上寫入一個字節(jié)時使應用程序收到一個通知(通過一個異常條件)。該標志有一些非常巧妙的用法。Windows?2000在創(chuàng)建線程堆棧時使用該標志。關于該標志的詳細說明,參見第1?6章。
? ? Windows?98?Windows?98將忽略PA?G?E?_?N?O?C?A?C?H?E、PA?G?E?_?W?R?I?T?E?C?O?M?B?I?N?E和PA?G?E?_?G?U?A?R?D這3個保護屬性標志。
.
.
.
13.8?數(shù)據(jù)對齊的重要性
? ? 本節(jié)不再討論進程的虛擬地址空間問題,而是要介紹數(shù)據(jù)對齊的重要性。數(shù)據(jù)對齊并不是操作系統(tǒng)的內存結構的一部分,而是C?P?U結構的一部分。
? ? 當C?P?U訪問正確對齊的數(shù)據(jù)時,它的運行效率最高。當數(shù)據(jù)大小的數(shù)據(jù)模數(shù)的內存地址是0時,數(shù)據(jù)是對齊的。例如,W?O?R?D值應該總是從被2除盡的地址開始,而D?W?O?R?D值應該總是從被4除盡的地址開始,如此等等。當?C?P?U試圖讀取的數(shù)據(jù)值沒有正確對齊時,?C?P?U可以執(zhí)行兩種操作之一。即它可以產(chǎn)生一個異常條件,也可以執(zhí)行多次對齊的內存訪問,以便讀取完整的未對齊數(shù)據(jù)值。
? ? 下面是訪問未對齊數(shù)據(jù)的某個代碼:
? ? 顯然,如果C?P?U執(zhí)行多次內存訪問,應用程序的運行速度就會放慢。在最好的情況下,系統(tǒng)訪問未對齊的數(shù)據(jù)所需要的時間將是訪問對齊數(shù)據(jù)的時間的兩倍,不過在有些情況下,訪問時間可能更長。為了使應用程序獲得最佳的運行性能,編寫的代碼必須使數(shù)據(jù)正確地對齊。
? ? 下面讓我們更加深入地說明x86?CPU是如何進行數(shù)據(jù)對齊的。X86?CPU的E?F?L?A?G?S寄存器中包含一個特殊的位標志,稱為?A?C(對齊檢查的英文縮寫)標志。按照默認設置,當?C?P?U首次加電時,該標志被設置為0。當該標志是0時,C?P?U能夠自動執(zhí)行它應該執(zhí)行的操作,以便成功地訪問未對齊的數(shù)據(jù)值。然而,如果該標志被設置為?1,每當系統(tǒng)試圖訪問未對齊的數(shù)據(jù)時,C?P?U就會發(fā)出一個INT?17H中斷。x?8?6的Windows?2000和Windows?98版本從來不改變這個C?P?U標志位。因此,當應用程序在?x?8?6處理器上運行時,你根本看不到應用程序中出現(xiàn)數(shù)據(jù)未對齊的異常條件。
? ? 現(xiàn)在讓我們來看一看?Alpha?CPU的情況。Alpha?CPU不能自動處理對未對齊數(shù)據(jù)的訪問。當未對齊的數(shù)據(jù)訪問發(fā)生時,C?P?U就會將這一情況通知操作系統(tǒng)。這時,Windows?2000將會確定它是否應該引發(fā)一個數(shù)據(jù)未對齊異常條件。它也可以執(zhí)行一些輔助指令,對問題默默地加以糾正,并讓你的代碼繼續(xù)運行。按照默認設置,當在?A?l?p?h?a計算機上安裝Windows?2000時,操作系統(tǒng)會對未對齊數(shù)據(jù)的訪問默默地進行糾正。然而,可以改變這個行為特性。當引導Windows?2000時,系統(tǒng)就會在注冊表中查找的這個關鍵字:
?
在這個關鍵字中,可能存在一個值,稱為?E?n?a?b?l?e?A?l?i?g?n?m?e?n?t?F?a?u?l?t?E?x?c?e?p?t?i?o?n?s。如果這個值不存在(這是通常的情況),Windows?2000會默默地處理對未對齊數(shù)據(jù)的訪問。如果存在這個值,系統(tǒng)就能獲取它的相關數(shù)據(jù)值。如果數(shù)據(jù)值是?0,系統(tǒng)會默默地進行訪問的處理。如果數(shù)據(jù)值是1,系統(tǒng)將不執(zhí)行默默的處理,而是引發(fā)一個未對齊異常條件。幾乎從來都不需要修改該注冊表值的數(shù)據(jù)值,因為如果修改有些應用程序能夠引發(fā)數(shù)據(jù)未對齊的異常條件并終止運行。
如果不使用A?X?PA?l?i?g?n實用程序,仍然可以讓系統(tǒng)為進程中的所有線程默默地糾正對未對齊數(shù)據(jù)的訪問,方法是讓進程的線程調用S?e?t?E?r?r?o?r?M?o?d?e函數(shù):
?
? ? 就我們的討論來說,需要說明的標志是S?E?M?_?N?O?A?L?I?G?N?M?E?N?T?FA?U?LT?E?X?C?E?P?T標志。當該標志設定后,系統(tǒng)會將自動糾正對未對齊數(shù)據(jù)的訪問。當該標志重新設置時,系統(tǒng)將不糾正對未對齊數(shù)據(jù)的訪問,而是引發(fā)數(shù)據(jù)未對齊異常條件。注意,修改該標志將會影響擁有調用該函數(shù)的線程的進程中包含的所有線程。換句話說,改變該標志不會影響其他進程中的任何線程。還要注意,進程的錯誤方式標志是由所有的子進程繼承的。因此,在調用?C?r?e?a?t?e?P?r?o?c?e?s?s函數(shù)之前,必須臨時重置該標志(不過通常不必這樣做)。
? ? 當然,無論在哪個?C?P?U平臺上運行,都可以調用?S?e?t?E?r?r?o?r?M?o?d?e函數(shù),傳遞?S?E?M?_N?O?A?L?I?G?N?M?E?N?T?FA?U?LT?E?X?C?E?P?T標志。但是,結果并不總是相同。如果是?x?8?6系統(tǒng),該標志總是打開的,并且不能被關閉。如果是?A?l?p?h?a系統(tǒng),那么只有當EnableAlignmentFault?Exceptions注冊表值被設置為1時,才能關閉該標志。
? ? M?i?c?r?o?s?o?f?t用于Alpha?CPU的C?/?C?+?+編譯器支持一個特殊的關鍵字,稱為?_?_?u?n?a?l?i?g?n?e?d。可以像使用c?o?n?s?t或v?o?l?a?t?i?l?e修改符那樣使用_?_?u?n?a?l?i?g?n?e?d修改符,差別在于_?_?u?n?a?l?i?g?n?e?d修改符只有在用于指針變量時才起作用。當通過未對齊指針來訪問數(shù)據(jù)時,編譯器就會生成一個代碼,該代碼假設數(shù)據(jù)沒有正確對齊,因此添加一些訪問數(shù)據(jù)時必須使用的輔助?C?P?U指令。下面顯示的代碼是前面已經(jīng)講過的代碼的修改版。這個新版本利用了關鍵字?_?_?u?n?a?l?i?g?n?e?d。
?
總結
以上是生活随笔為你收集整理的Windowsw核心编程 第13章 Windows内存结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#-CHTTPDownload
- 下一篇: C#-播放器相关