Windows核心编程 第十五章 在应用程序中使用虚拟内存
第1?5章?在應(yīng)用程序中使用虛擬內(nèi)存
????Wi?n?d?o?w?s提供了3種進行內(nèi)存管理的方法,它們是:
??????虛擬內(nèi)存,最適合用來管理大型對象或結(jié)構(gòu)數(shù)組。
??????內(nèi)存映射文件,最適合用來管理大型數(shù)據(jù)流(通常來自文件)以及在單個計算機上運行的多個進程之間共享數(shù)據(jù)。
??????內(nèi)存堆棧,最適合用來管理大量的小對象。
本章將要介紹第一種方法,即虛擬內(nèi)存。內(nèi)存映射文件和堆棧分別在第?1?7章和第1?8章介紹。用于管理虛擬內(nèi)存的函數(shù)可以用來直接保留一個地址空間區(qū)域,將物理存儲器(來自頁文件)提交給該區(qū)域,并且可以設(shè)置你自己的保護屬性。
5.1?在地址空間中保留一個區(qū)域
??通過調(diào)用Vi?r?t?u?a?l?A?l?l?o?c函數(shù),可以在進程的地址空間中保留一個區(qū)域:
? ? 第一個參數(shù)p?v?A?d?d?r?e?s?s包含一個內(nèi)存地址,用于設(shè)定想讓系統(tǒng)將地址空間保留在什么地方。在大多數(shù)情況下,你為該參數(shù)傳遞NU?L?L。它告訴Vi?r?t?u?a?l?A?l?l?o?c,保存著一個空閑地址區(qū)域的記錄的系統(tǒng)應(yīng)該將區(qū)域保留在它認為合適的任何地方。系統(tǒng)可以從進程的地址空間的任何位置來保留一個區(qū)域,因為不能保證系統(tǒng)可以從地址空間的底部向上或者從上面向底部來分配各個區(qū)域。可以使用M?E?M?_?TO?P?_?D?O?W?N標(biāo)志來說明該分配方式。這個標(biāo)志將在本章的后面加以介紹。
? ? 對大多數(shù)程序員來說,能夠選擇一個特定的內(nèi)存地址,并在該地址保留一個區(qū)域,這是個非同尋常的想法。當(dāng)你在過去分配內(nèi)存時,操作系統(tǒng)只是尋找一個其大小足以滿足需要的內(nèi)存塊,并分配該內(nèi)存塊,然后返回它的地址。但是,由于每個進程有它自己的地址空間,因此可以設(shè)定一個基本內(nèi)存地址,在這個地址上讓操作系統(tǒng)保留地址空間區(qū)域。
? ? 例如,你想將一個從50?MB開始的區(qū)域保留在進程的地址空間中。這時,可以傳遞52?428?800(5?0?×?1?0?2?4?×?1?0?2?4)作為p?v?A?d?d?r?e?s?s參數(shù)。如果該內(nèi)存地址有一個足夠大的空閑區(qū)域滿足你的要求,那么系統(tǒng)就保留這個區(qū)域并返回。如果在特定的地址上不存在空閑區(qū)域,或者如果空閑區(qū)域不夠大,那么系統(tǒng)就不能滿足你的要求,Vi?r?t?u?a?l?A?l?l?o?c函數(shù)返回N?U?L?L。注意,為p?v?A?d?d?r?e?s?s參數(shù)傳遞的任何地址必須始終位于進程的用戶方式分區(qū)中,否則對?Vi?r?t?u?a?l?A?l?l?o?c函數(shù)的調(diào)用就會失敗,導(dǎo)致它返回N?U?L?L。
? ? 第1?3章講過,地址空間區(qū)域總是按照分配粒度的邊界來保留的(迄今為止在所有的Wi?n?d?o?w?s環(huán)境下均是6?4?K?B?)。因此,如果試圖在進程地址空間中保留一個從19?668?992(300?×?65?536?+?8192)這個地址開始的區(qū)域,系統(tǒng)就會將這個地址圓整為?6?4?K?B的倍數(shù),然后保留從19?660?800(3?0?0×?65?536)這個地址開始的區(qū)域。
? ? 如果Vi?r?t?u?a?l?A?l?l?o?c函數(shù)能夠滿足你的要求,那么它就返回一個值,指明保留區(qū)域的基地址。如果傳遞一個特定的地址作為?Vi?r?t?u?a?l?A?l?l?o?c的p?v?A?d?d?r?e?s?s參數(shù),那么該返回值與傳遞給Vi?r?t?u?a?l?A?l?l?o?c的值相同,并被圓整為(如果需要的話)6?4?K?B邊界值。
? ? Vi?r?t?u?a?l?A?l?l?o?c函數(shù)的第二個參數(shù)是d?w?S?i?z?e,用于設(shè)定想保留的區(qū)域的大小(以字節(jié)為計量單位)。由于系統(tǒng)保留的區(qū)域始終必須是?C?P?U頁面大小的倍數(shù),因此,如果試圖保留一個跨越6?2?K?B的區(qū)域,結(jié)果就會在使用?4?KB、8?KB或16?KB頁面的計算機上產(chǎn)生一個跨越?6?4?K?B的區(qū)域。
? ? Vi?r?t?u?a?l?A?l?l?o?c函數(shù)的第三個參數(shù)是f?d?w?A?l?l?o?c?a?t?i?o?n?Ty?p?e,它能夠告訴系統(tǒng)你想保留一個區(qū)域還是提交物理存儲器(這樣的區(qū)分是必要的,因為Vi?r?t?u?a?l?A?l?l?o?c函數(shù)也可以用來提交物理存儲器)。若要保留一個地址空間區(qū)域,必須傳遞?M?E?M?_?R?E?S?E?RV?E標(biāo)識符作為F?d?w?A?l?l?o?c?a?t?i?o?n?Ty?p?e參數(shù)的值。
? ? 如果保留的區(qū)域預(yù)計在很長時間內(nèi)不會被釋放,那么可以在盡可能高的內(nèi)存地址上保留該區(qū)域。這樣,該區(qū)域就不會從進程地址空間的中間位置上進行保留。因為在這個位置上它可能導(dǎo)致區(qū)域分成碎片。如果想讓系統(tǒng)在最高內(nèi)存地址上保留一個區(qū)域,必須為?p?v?A?d?d?r?e?s?s參數(shù)和f?d?w?A?l?l?o?c?a?t?i?o?n?Ty?p?e參數(shù)傳遞?N?U?L?L,還必須逐位使用?O?R將M?E?M?_?TO?P?_?D?O?W?N標(biāo)志和M?E?M?_?R?E?S?E?RV?E標(biāo)志連接起來。
? ? 注意?在Windows?98下,M?E?M?_?TO?P?_?D?O?W?N標(biāo)志將被忽略。
? ? 最后一個參數(shù)是f?d?w?P?r?o?t?e?c?t,用于指明應(yīng)該賦予該地址空間區(qū)域的保護屬性。與該區(qū)域相關(guān)聯(lián)的保護屬性對映射到該區(qū)域的已提交內(nèi)存沒有影響。無論賦予區(qū)域的保護屬性是什么,如果沒有提交任何物理存儲器,那么訪問該范圍中的內(nèi)存地址的任何企圖都將導(dǎo)致該線程引發(fā)一個訪問違規(guī)。
? ? 當(dāng)保留一個區(qū)域時,應(yīng)該為該區(qū)域賦予一個已提交內(nèi)存最常用的保護屬性。例如,如果打算提交的物理存儲器的保護屬性是?PA?G?E?_?R?E?A?D?W?R?I?T?E(這是最常用的保護屬性),那么應(yīng)該用PA?G?E?_?R?E?A?D?W?R?I?T?E保護屬性來保留該區(qū)域。當(dāng)區(qū)域的保護屬性與已提交內(nèi)存的保護屬性相匹配時,系統(tǒng)保存的內(nèi)部記錄的運行效率最高。
? ? 可以使用下列保護屬性中的任何一個:?PA?G?E?_?N?O?A?C?C?E?S?S、PA?G?E?_?R?E?A?D?W?R?I?T?E、PA?G?E?_?R?E?A?D?O?N?LY、PA?G?E?_?E?X?E?C?U?T?E、PA?G?E?_?E?X?E?C?U?T?E?_?R?E?A?D或PA?G?E?_?E?X?E?C?U?T?E?_R?E?A?D?W?R?I?T?E。但是,既不能設(shè)定?PA?G?E?_?W?R?I?T?E?C?O?P?Y屬性,也不能設(shè)定PA?G?E?_?E?X?E?C?U?T?E?_W?R?I?T?E?C?O?P?Y屬性。如果設(shè)定了這些屬性,Vi?r?t?u?a?l?A?l?l?o?c函數(shù)將不保留該區(qū)域,并且返回N?U?L?L。另外,當(dāng)保留地址空間區(qū)域時,不能使用保護屬性標(biāo)志?PA?G?E?_?G?U?A?R?D,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,這些標(biāo)志只能用于已提交的內(nèi)存。
注意?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保護屬性。如果試圖保留使用?PA?G?E?_?E?X?E?C?U?T?E或PA?G?E?_?E?X?E?C?U?T?E?_?R?E?A?D兩個保護屬性的區(qū)域,將會產(chǎn)生一個帶有?PA?G?E?_?R?E?A?D?O?N?LY保護屬性的區(qū)域。同樣,如果保留一個使用?PA?G?E?_?E?X?E?C?U?T?E?_?R?E?A?D?W?R?I?T?E保護屬性的區(qū)域,就會產(chǎn)生一個帶有PA?G?E?_?R?E?A?D?W?R?I?T?E保護屬性的區(qū)域。
15.2?在保留區(qū)域中的提交存儲器
? ? 當(dāng)保留一個區(qū)域后,必須將物理存儲器提交給該區(qū)域,然后才能訪問該區(qū)域中包含的內(nèi)存地址。系統(tǒng)從它的頁文件中將已提交的物理存儲器分配給一個區(qū)域。物理存儲器總是按頁面邊界和頁面大小的塊來提交的。
? ? 若要提交物理存儲器,必須再次調(diào)用Vi?r?t?u?a?l?A?l?l?o?c函數(shù)。不過這次為f?d?w?A?l?l?o?c?a?t?i?o?n?Ty?p?e參數(shù)傳遞的是M?E?M?_?C?O?M?M?I?T標(biāo)志,而不是M?E?M?_?R?E?S?E?RV?E標(biāo)志。傳遞的頁面保護屬性通常與調(diào)用Vi?r?t?u?a?l?A?l?l?o?c來保留區(qū)域時使用的保護屬性相同(大多數(shù)情況下是?PA?G?E?_?R?E?A?D?W?R?I?T?E),不過也可以設(shè)定一個不同的保護屬性。
? ? 在已保留的區(qū)域中,你必須告訴Vi?r?t?u?a?l?A?l?l?o?c函數(shù),你想將物理存儲器提交到何處,以及要提交多少物理存儲器。為了做到這一點,可以在?p?v?A?d?d?r?e?s?s參數(shù)中設(shè)定你需要的內(nèi)存地址,并在d?w?S?i?z?e參數(shù)中設(shè)定物理存儲器的數(shù)量(以字節(jié)為計量單位)。注意,不必立即將物理存儲器提交給整個區(qū)域。
? ? 下面讓我們來看一個如何提交物理存儲器。比如說,你的應(yīng)用程序是在?x86?CPU上運行的,該應(yīng)用程序保留了一個從地址?5?242?880開始的512?KB的區(qū)域。你想讓應(yīng)用程序?qū)⑽锢泶鎯ζ魈峤唤o已保留區(qū)域的6?KB部分,從2?KB的地方開始,直到已保留區(qū)域的地址空間。為此,可以調(diào)用帶有M?E?M?_?C?O?M?M?I?T標(biāo)志的Vi?r?t?u?a?l?A?l?l?o?c函數(shù),如下所示:
?
? ? 在這個例子中,系統(tǒng)必須提交8?KB的物理存儲器,地址范圍從5?242?880到5?251?071?(5?242880?+?8?KB??-?1字節(jié))。這兩個提交的頁面都擁有PA?G?E?_?R?E?A?D?W?R?I?T?E保護屬性。保護屬性只以整個頁面為單位來賦予。同一個內(nèi)存頁面的不同部分不能使用不同的保護屬性。然而,區(qū)域中的一個頁面可以使用一種保護屬性(比如?PA?G?E?_?R?E?A?D?W?R?I?T?E),而同一個區(qū)域中的另一個頁面可以使用不同的保護屬性(比如PA?G?E?_?R?E?A?D?O?N?LY)。
15.3?同時進行區(qū)域的保留和內(nèi)存的提交
有時你可能想要在保留區(qū)域的同時,將物理存儲器提交給它。只需要一次調(diào)用?Vi?r?t?u?a?l?A?l?l?o?c函數(shù)就能進行這樣的操作,如下所示:
?
? ? 這個函數(shù)調(diào)用請求保留一個?99?KB的區(qū)域,并且將99?KB的物理存儲器提交給它。當(dāng)系統(tǒng)處理這個函數(shù)調(diào)用時,它首先要搜索你的進程的地址空間,找出未保留的地址空間中一個地址連續(xù)的區(qū)域,它必須足夠大,能夠存放100?KB(在4?KB頁面的計算機上)或104?KB(在8?KB頁面的計算機上)。
? ? 系統(tǒng)之所以要搜索地址空間,原因是已將?p?v?A?d?d?r?e?s?s參數(shù)設(shè)定為N?U?L?L。如果為p?v?A?d?d?r?e?s?s設(shè)定了內(nèi)存地址,系統(tǒng)就要查看在該內(nèi)存地址上是否存在足夠大的未保留地址空間。如果系統(tǒng)找不到足夠大的未保留地址空間,Vi?r?t?u?a?l?A?l?l?o?c將返回N?U?L?L,
? ? 如果能夠保留一個合適的區(qū)域,系統(tǒng)就將物理存儲器提交給整個區(qū)域。無論是該區(qū)域還是提交的內(nèi)存,都將被賦予PA?G?E?_?R?E?A?D?W?R?I?T?E保護屬性。
? ? 最后需要說明的是,Vi?r?t?u?a?l?A?l?l?o?c將返回保留區(qū)域和提交區(qū)域的虛擬地址,然后該虛擬地址被保存在p?v?M?e?m變量中。如果系統(tǒng)無法找到足夠大的地址空間,或者不能提交該物理存儲器,Vi?r?t?u?a?l?A?l?l?o?c將返回N?U?L?L。
? ? 當(dāng)用這種方式來保留一個區(qū)域和提交物理存儲器時,將特定的地址作為?p?v?A?d?d?r?e?s?s參數(shù)傳遞給?Vi?r?t?u?a?l?A?l?l?o?c當(dāng)然是可能的。否則就必須用?O?R將M?E?M?_?TO?P?_?D?O?W?N?標(biāo)志與f?d?w?A?l?l?o?c?a?t?i?o?n?Ty?p?e參數(shù)連接起來,并為p?v?A?d?d?r?e?s?s參數(shù)傳遞N?U?L?L,讓系統(tǒng)在進程的地址空間的頂部選定一個適當(dāng)?shù)膮^(qū)域。
15.4?何時提交物理存儲器
? ? 假設(shè)想實現(xiàn)一個電子表格應(yīng)用程序,這個電子表格為?2?0?0行?x?256列。對于每一個單元格,都需要一個C?E?L?L?D?ATA結(jié)構(gòu)來描述單元格的內(nèi)容。若要處理這種二維單元格矩陣,最容易的方法是在應(yīng)用程序中聲明下面的變量:
?
? ? 如果C?E?L?L?D?ATA結(jié)構(gòu)的大小是1?2?8字節(jié),那么這個二維矩陣將需要6?553?600(200?x?256?x1?2?8)個字節(jié)的物理存儲器。對于電子表格來說,如果直接用頁文件來分配物理存儲器,那么這是個不小的數(shù)目了,尤其是考慮到大多數(shù)用戶只是將信息放入少數(shù)的單元格中,而大部分單元格卻空閑不用,因此顯得有些浪費。內(nèi)存的利用率非常低。
? ? 傳統(tǒng)上,電子表格一直是用其他數(shù)據(jù)結(jié)構(gòu)技術(shù)來實現(xiàn)的,比如鏈接表等。使用鏈接表,只需要為電子表格中實際包含數(shù)據(jù)的單元格創(chuàng)建C?E?L?L?D?ATA結(jié)構(gòu)。由于電子表格中的大多數(shù)單元格都是不用的,因此這種方法可以節(jié)省大量的內(nèi)存。但是這種方法使得你很難獲得單元格的內(nèi)容。如果想知道第5行第1?0列的單元格的內(nèi)容,必須遍歷鏈接表,才能找到需要的單元格,因此使用鏈接表方法比明確聲明的矩陣方法速度要慢。
? ? 虛擬內(nèi)存為我們提供了一種兼顧預(yù)先聲明二維矩陣和實現(xiàn)鏈接表的兩全其美的方法。運用虛擬內(nèi)存,既可以使用已聲明的矩陣技術(shù)進行快速而方便的訪問,又可以利用鏈接表技術(shù)大大節(jié)省內(nèi)存的使用量。
? ? 如果想利用虛擬內(nèi)存技術(shù)的優(yōu)點,你的程序必須按照下列步驟來編寫:
? ? 1)?保留一個足夠大的地址空間區(qū)域,用來存放?C?E?L?L?D?ATA結(jié)構(gòu)的整個數(shù)組。保留一個根本不使用任何物理存儲器的區(qū)域。
? ? 2)?當(dāng)用戶將數(shù)據(jù)輸入一個單元格時,找出?C?E?L?L?D?ATA結(jié)構(gòu)應(yīng)該進入的保留區(qū)域中的內(nèi)存地址。當(dāng)然,這時尚未有任何物理存儲器被映射到該地址,因此,訪問該地址的內(nèi)存的任何企圖都會引發(fā)訪問違規(guī)。
? ? 3)?就C?E?L?L?D?ATA結(jié)構(gòu)來說,只將足夠的物理存儲器提交給第二步中找到的內(nèi)存地址(你可以告訴系統(tǒng)將物理存儲器提交給保留區(qū)域的特定部分,這個區(qū)域既可以包含映射到物理存儲器的各個部分,也可以包含沒有映射到物理存儲器的各個部分)。
? ? 4)?設(shè)置新的C?E?L?L?D?ATA結(jié)構(gòu)的成員。
? ? 現(xiàn)在物理存儲器已經(jīng)映射到相應(yīng)的位置,你的程序能夠訪問內(nèi)存,而不會引發(fā)訪問違規(guī)。
這個虛擬內(nèi)存技術(shù)非常出色,因為只有在用戶將數(shù)據(jù)輸入電子表格的單元格時,才會提交物理存儲器。由于電子表格中的大多數(shù)單元格是空的,因此大部分保留區(qū)域沒有提交給它的物理存儲器。
? ? 虛擬內(nèi)存技術(shù)存在的一個問題是,必須確定物理存儲器在何時提交。如果用戶將數(shù)據(jù)輸入一個單元格,然后只是編輯或修改該數(shù)據(jù),那么就沒有必要提交物理存儲器,因為該單元格的C?E?L?L?D?ATA結(jié)構(gòu)的內(nèi)存在數(shù)據(jù)初次輸入時就已經(jīng)提交了。
? ? 另外,系統(tǒng)總是按頁面的分配粒度來提交物理存儲器的。因此,當(dāng)試圖為單個?C?E?L?L?D?ATA結(jié)構(gòu)提交物理存儲器時(像上面的第二步那樣),系統(tǒng)實際上提交的是內(nèi)存的一個完整的頁面。這并不像它聽起來那樣十分浪費:為單個C?E?L?L?D?ATA結(jié)構(gòu)提交物理存儲器的結(jié)果是,也要為附近的其他C?E?L?L?D?ATA結(jié)構(gòu)提交內(nèi)存。如果這時用戶將數(shù)據(jù)輸入鄰近的單元格(這是經(jīng)常出現(xiàn)的情況),就不需要提交更多的物理存儲器。
有4種方法可以用來確定是否要將物理存儲器提交給區(qū)域的一個部分:
? ? ??始終設(shè)法進行物理存儲器的提交。每次調(diào)用?Vi?r?t?u?a?l?A?l?l?o?c函數(shù)的時候,不要查看物理存儲器是否已經(jīng)映射到地址空間區(qū)域的一個部分,而是讓你的程序設(shè)法進行內(nèi)存的提交。系統(tǒng)首先查看內(nèi)存是否已經(jīng)被提交,如果已經(jīng)提交,那么就不要提交更多的物理存儲器。
這種方法最容易操作,但是它的缺點是每次改變?C?E?L?L?D?ATA結(jié)構(gòu)時要多進行一次函數(shù)的
調(diào)用,這會使程序運行得比較慢。
??????(使用Vi?r?t?u?a?l?Q?u?e?r?y函數(shù))確定物理存儲器是否已經(jīng)提交給包含C?E?L?L?D?ATA結(jié)構(gòu)的地址空間。如果已經(jīng)提交了,那么就不要進行任何別的操作。如果尚未提交,則可以調(diào)用Vi?r?t?u?a?l?A?l?l?o?c函數(shù)以便提交內(nèi)存。這種方法實際上比第一種方法差,它既會增加代碼的長度,又會降低程序運行的速度(因為增加了對Vi?r?t?u?a?l?A?l?l?o?c函數(shù)的調(diào)用)。
??????保留一個關(guān)于哪些頁面已經(jīng)提交和哪些頁面尚未提交的記錄。這樣做可以使你的應(yīng)用程序運行得更快,因為不必調(diào)用?Vi?r?t?u?a?l?A?l?l?o?c函數(shù),你的代碼能夠比系統(tǒng)更快地確定內(nèi)存是否已經(jīng)被提交。它的缺點是,必須不斷跟蹤頁面提交的信息,這可能非常簡單,也可能非常困難,要根據(jù)你的情況而定。
??????使用結(jié)構(gòu)化異常處理(?S?E?H)方法,這是最好的方法。?S?E?H是一個操作系統(tǒng)特性,它使系統(tǒng)能夠在發(fā)生某種情況時將此情況通知你的應(yīng)用程序。實際上可以創(chuàng)建一個帶有異常
處理程序的應(yīng)用程序,然后,每當(dāng)試圖訪問未提交的內(nèi)存時,系統(tǒng)就將這個問題通知應(yīng)用程序。然后你的應(yīng)用程序便進行內(nèi)存的提交,并告訴系統(tǒng)重新運行導(dǎo)致異常條件的指令。這時對內(nèi)存的訪問就能成功地進行了,程序?qū)⒗^續(xù)運行,仿佛從未發(fā)生過問題一樣。這種方法是優(yōu)點最多的方法,因為需要做的工作最少(也就是說要你編寫的代碼比較少),同時,你的程序可以全速運行。關(guān)于?S?E?H的全面介紹,請參見第2?3、2?4和2?5章。第2?5章中的電子表格示例應(yīng)用程序說明了如何按照上面介紹的方法來使用虛擬內(nèi)存。
15.5?回收虛擬內(nèi)存和釋放地址空間區(qū)域
? ? 若要回收映射到一個區(qū)域的物理存儲器,或者釋放這個地址空間區(qū)域,可調(diào)用?Vi?r?t?u?a?l?F?r?e?e函數(shù):
? ? 首先讓我們觀察一下調(diào)用Vi?r?t?u?a?l?F?r?e?e函數(shù)來釋放一個已保留區(qū)域的簡單例子。當(dāng)你的進程不再訪問區(qū)域中的物理存儲器時,就可以釋放整個保留的區(qū)域和所有提交給該區(qū)域的物理存儲器,方法是一次調(diào)用Vi?r?t?u?a?l?F?r?e?e函數(shù)。
? ? 就這個函數(shù)的調(diào)用來說,?p?v?A?d?d?r?e?s?s參數(shù)必須是該區(qū)域的基地址。此地址與該區(qū)域被保留時Vi?r?t?u?a?l?A?l?l?o?c函數(shù)返回的地址相同。系統(tǒng)知道在特定內(nèi)存地址上的該區(qū)域的大小,因此可以為d?w?S?i?z?e參數(shù)傳遞0。實際上,必須為d?w?S?i?z?e參數(shù)傳遞0,否則對Vi?r?t?u?a?l?F?r?e?e的調(diào)用就會失敗。對于第三個參數(shù)f?d?w?F?r?e?e?Ty?p?e,必須傳遞M?E?M?_?R?E?L?E?A?S?E,以告訴系統(tǒng)將所有映射的物理存儲器提交給該區(qū)域并釋放該區(qū)域。當(dāng)釋放一個區(qū)域時,必須釋放該區(qū)域保留的所有地址空間。例如不能保留一個128?KB的區(qū)域,然后決定只釋放它的64?KB。必須釋放所有的128?KB。
? ? 當(dāng)想要從一個區(qū)域回收某些物理存儲器,但是卻不釋放該區(qū)域時,也可以調(diào)用?Vi?r?t?u?a?l?F?r?e?e函數(shù),若要回收某些物理存儲器,必須在Vi?r?t?u?a?l?F?r?e?e函數(shù)的p?v?A?d?d?r?e?s?s參數(shù)中傳遞用于標(biāo)識要回收的第一個頁面的內(nèi)存地址,還必須在?d?w?S?i?z?e參數(shù)中設(shè)定要釋放的字節(jié)數(shù),并在?f?d?w?F?r?e?e?Ty?p?e參數(shù)中傳遞M?E?M?_?D?E?C?O?M?M?I?T標(biāo)志。
? ? 與提交物理存儲器的情況一樣,回收時也必須按照頁面的分配粒度來進行。這就是說,設(shè)
定頁面中間的一個內(nèi)存地址就可以回收整個頁面。當(dāng)然,如果?pvAddress?+?dwSize的值位于一個頁面的中間,那么包含該地址的整個頁面將被回收。因此位于?pvAddress?至pvAddress?+d?w?S?i?z?e范圍內(nèi)的所有頁面均被回收。
? ? 如果d?w?S?i?z?e是0,p?v?S?d?d?r?e?s?s是已分配區(qū)域的基地址,那么?Vi?r?t?u?a?l?F?r?e?e將回收全部范圍內(nèi)的已分配頁面。當(dāng)物理存儲器的頁面已經(jīng)回收之后,已釋放的物理存儲器就可以供系統(tǒng)中的所有其他進程使用,如果試圖訪問未回收的內(nèi)存,將會造成訪問違規(guī)。
15.6?改變保護屬性
? ? 雖然實踐中很少這樣做,但是可以改變已經(jīng)提交的物理存儲器的一個或多個頁面的保護屬性。例如,你編寫了一個用于管理鏈接表的代碼,將它的節(jié)點存放在一個保留區(qū)域中。可以設(shè)計一些函數(shù),以便處理該鏈接表,這樣,它們就可以在每個函數(shù)開始運行時將已提交內(nèi)存的保護屬性改為?PA?G?E?_?R?E?A?D?W?R?I?T?E,然后在每個函數(shù)終止運行時將保護屬性重新改為PA?G?E?_?N?O?A?C?C?E?S?S。
? ? 通過這樣的設(shè)置,就能夠使鏈接表數(shù)據(jù)不受隱藏在程序中的其他錯誤的影響。如果進程中的任何其他代碼存在一個迷失指針,試圖訪問你的鏈接表數(shù)據(jù),那么就會引發(fā)訪問違規(guī)。當(dāng)試圖尋找應(yīng)用程序中難以發(fā)現(xiàn)的錯誤時,利用保護屬性是極其有用的。
? ? 若要改變內(nèi)存頁面的保護屬性,可以調(diào)用Vi?r?t?u?a?l?P?r?o?t?e?c?t函數(shù):
? ? 這里的p?v?A?d?d?r?e?s?s參數(shù)指向內(nèi)存的基地址(它必須位于進程的用戶方式分區(qū)中),d?w?S?i?z?e參數(shù)用于指明你想要改變保護屬性的字節(jié)數(shù),而?f?l?N?e?w?P?r?o?t?e?c?t參數(shù)則代表PA?G?E?_?*保護屬性標(biāo)志中的任何一個標(biāo)志,但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這兩個標(biāo)志除外。
? ? 最后一個參數(shù)p?f?l?O?l?d?P?r?o?t?e?c?t是D?W?O?R?D的地址,Vi?r?t?u?a?l?P?r?o?t?e?c?t將用原先與p?v?A?d?d?r?e?s?s位置上的字節(jié)相關(guān)的保護屬性填入該D?W?O?R?D。盡管許多應(yīng)用程序并不需要該信息,但是必須為該參數(shù)傳遞一個有效地址,否則該函數(shù)的運行將會失敗。
? ? 當(dāng)然,保護屬性是與內(nèi)存的整個頁面相關(guān)聯(lián)的,而不是賦予內(nèi)存的各個字節(jié)的。因此,如
果要使用下面的代碼來調(diào)用?4?KB?頁面的計算機上的?Vi?r?t?u?a?l?P?r?o?t?e?c?t函數(shù),其結(jié)果是把PA?G?E?_?N?O?A?C?C?E?S?S保護屬性賦予內(nèi)存的兩個頁面:
? ? Windows?98?Windows?98只支持PA?G?E?_?R?E?A?D?O?N?LY和PA?G?E?_?R?E?A?D?W?R?I?T?E兩個保護屬性。如果試圖將頁面的保護屬性改為PA?G?E?_?E?X?E?C?U?T?E或PA?G?E?_?E?X?E?C?U?T?E?_?R?E?A?D,該頁面可得到PA?G?E?_?R?E?A?D?O?N?LY保護屬性。同樣,如果試圖將頁面的保護屬性改為PA?G?E?_?E?X?E?C?U?T?E?_?R?E?A?D?W?R?I?T?E,那么該頁面將得到PA?G?E?_?R?E?A?D?W?R?I?T?E保護屬性。
? ? Vi?r?t?u?a?l?P?r?o?t?e?c?t函數(shù)不能用于改變跨越不同保留區(qū)域的頁面的保護屬性。如果擁有相鄰的保留區(qū)域并想改變這些區(qū)域中的一些頁面的保護屬性,那么必須多次調(diào)用?Vi?r?t?u?a?l?P?r?o?t?e?c?t函數(shù)。
總結(jié)
以上是生活随笔為你收集整理的Windows核心编程 第十五章 在应用程序中使用虚拟内存的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows核心编程 第十四章 虚拟内
- 下一篇: Windows核心编程 第十七章 -内存