




已閱讀5頁,還剩33頁未讀, 繼續(xù)免費閱讀
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
第15章 在應(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è)置你自己的保護屬性。15.1 在地址空間中保留一個區(qū)域通過調(diào)用Vi r t u a l A l l o c函數(shù),可以在進程的地址空間中保留一個區(qū)域:PVOID VirtualAlloc( PVOID pvAddress, SIZE_T dwSize, DWORD fdwAllocationType, DWORD fdwProtect);第一個參數(shù)p v A d d r e s s包含一個內(nèi)存地址,用于設(shè)定想讓系統(tǒng)將地址空間保留在什么地方。在大多數(shù)情況下,你為該參數(shù)傳遞M U 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ū)域??梢允褂肕 E M _ TO P _ D O W N標志來說明該分配方式。這個標志將在本章的后面加以介紹。對大多數(shù)程序員來說,能夠選擇一個特定的內(nèi)存地址,并在該地址保留一個區(qū)域,這是個非同尋常的想法。當你在過去分配內(nèi)存時,操作系統(tǒng)只是尋找一個其大小足以滿足需要的內(nèi)存塊,并分配該內(nèi)存塊,然后返回它的地址。但是,由于每個進程有它自己的地址空間,因此可以設(shè)定一個基本內(nèi)存地址,在這個地址上讓操作系統(tǒng)保留地址空間區(qū)域。例如,你想將一個從50 MB開始的區(qū)域保留在進程的地址空間中。這時,可以傳遞52 428 800(5 01 0 2 41 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)用就會失敗,導致它返回N U L L。第1 3章講過,地址空間區(qū)域總是按照分配粒度的邊界來保留的(迄今為止在所有的Wi n d o w s環(huán)境下均是6 4 K B )。因此,如果試圖在進程地址空間中保留一個從19 668 992(30065 536 + 8192)這個地址開始的區(qū)域,系統(tǒng)就會將這個地址圓整為6 4 K B的倍數(shù),然后保留從19 660 800(3 0 065 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標識符作為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ū)域就不會從進程地址空間的中間位置上進行保留。因為在這個位置上它可能導致區(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標志和M E M _ R E S E RV E標志連接起來。注意在Windows 98下,M E M _ TO P _ D O W N標志將被忽略。最后一個參數(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)存地址的任何企圖都將導致該線程引發(fā)一個訪問違規(guī)。當保留一個區(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ū)域。當區(qū)域的保護屬性與已提交內(nèi)存的保護屬性相匹配時,系統(tǒng)保存的內(nèi)部記錄的運行效率最高??梢允褂孟铝斜Wo屬性中的任何一個: 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。另外,當保留地址空間區(qū)域時,不能使用保護屬性標志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,這些標志只能用于已提交的內(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ū)域中的提交存儲器當保留一個區(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標志,而不是M E M _ R E S E RV E標志。傳遞的頁面保護屬性通常與調(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標志的Vi r t u a l A l l o c函數(shù),如下所示:VirtualAlloc(PVOID)(5242880 + (2 * 1024), 6 * 1024, MEM_COMMIT, PAGE_READWRITE);在這個例子中,系統(tǒng)必須提交8 KB的物理存儲器,地址范圍從5 242 880到5 251 071 (5 242 880 + 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ù)就能進行這樣的操作,如下所示:PVOID pvMem = VirtualAlloc(NULL, 99 * 1024, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);這個函數(shù)調(diào)用請求保留一個99 KB的區(qū)域,并且將99 KB的物理存儲器提交給它。當系統(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。當用這種方式來保留一個區(qū)域和提交物理存儲器時,將特定的地址作為p v A d d r e s s參數(shù)傳遞給Vi r t u a l A l l o c 當然是可能的。否則就必須用O R 將M E M _ TO P _ D O W N 標志與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)在進程的地址空間的頂部選定一個適當?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)用程序中聲明下面的變量:CELLDATA CellData200256;如果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) 當用戶將數(shù)據(jù)輸入一個單元格時,找出C E L L D ATA結(jié)構(gòu)應(yīng)該進入的保留區(qū)域中的內(nèi)存地址。當然,這時尚未有任何物理存儲器被映射到該地址,因此,訪問該地址的內(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)總是按頁面的分配粒度來提交物理存儲器的。因此,當試圖為單個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)用程序,然后,每當試圖訪問未提交的內(nèi)存時,系統(tǒng)就將這個問題通知應(yīng)用程序。然后你的應(yīng)用程序便進行內(nèi)存的提交,并告訴系統(tǒng)重新運行導致異常條件的指令。這時對內(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ù):BOOL VirtualFree( LPVOID pvAddress, SIZE_T dwSize, DWORD fdwFreeType);首先讓我們觀察一下調(diào)用Vi r t u a l F r e e函數(shù)來釋放一個已保留區(qū)域的簡單例子。當你的進程不再訪問區(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ū)域。當釋放一個區(qū)域時,必須釋放該區(qū)域保留的所有地址空間。例如不能保留一個128 KB的區(qū)域,然后決定只釋放它的64 KB。必須釋放所有的128 KB。當想要從一個區(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ù)中傳遞用于標識要回收的第一個頁面的內(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標志。與提交物理存儲器的情況一樣,回收時也必須按照頁面的分配粒度來進行。這就是說,設(shè)定頁面中間的一個內(nèi)存地址就可以回收整個頁面。當然,如果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)的已分配頁面。當物理存儲器的頁面已經(jīng)回收之后,已釋放的物理存儲器就可以供系統(tǒng)中的所有其他進程使用,如果試圖訪問未回收的內(nèi)存,將會造成訪問違規(guī)。15.5.1 何時回收物理存儲器在實踐中,知道何時回收內(nèi)存是非常困難的。讓我們再以電子表格為例。如果你的應(yīng)用程序是在x 8 6計算機上運行,每個內(nèi)存頁面是4 KB ,它可以存放3 2個(4 0 9 6 / 1 2 8)C E L L D ATA結(jié)構(gòu)。如果用戶刪除了單元格C e l l D a t a 0 1 的內(nèi)容,那么只要單元格C e l l D a t a 0 0 至C e l l D a t a 0 3 1 也不被使用,就可以回收它的內(nèi)存頁面。那么怎么能夠知道這個情況呢?可以用下面3種方法來解決這個問題。 毫無疑問,最容易的方法是設(shè)計一個C E L L D ATA結(jié)構(gòu),它的大小只有一個頁面。這時,由于始終都是每個頁面使用一個結(jié)構(gòu),因此當不再需要該結(jié)構(gòu)中的數(shù)據(jù)時,就可以回收該頁面的物理存儲器。即使你的數(shù)據(jù)結(jié)構(gòu)是x86 CPU上的8 KB或12 KB頁面的倍數(shù)(通常這是非常大的數(shù)據(jù)結(jié)構(gòu)),回收內(nèi)存仍然是非常容易的。當然,如果要使用這種方法,必須定義你的數(shù)據(jù)結(jié)構(gòu),使之符合你針對的C P U的頁面大小而不是我們通常編寫程序所用的結(jié)構(gòu)。 更為實用的方法是保留一個正在使用的結(jié)構(gòu)的記錄。為了節(jié)省內(nèi)存,可以使用一個位圖。這樣,如果有一個1 0 0個結(jié)構(gòu)的數(shù)組,你也可以維護一個1 0 0位的數(shù)組。開始時,所有的位均設(shè)置為0,表示這些結(jié)構(gòu)都沒有使用。當使用這些結(jié)構(gòu)時,可以將對應(yīng)的位設(shè)置為1。然后,每當不需要某個結(jié)構(gòu),并將它的位重新改為0時,你可以檢查屬于同一個內(nèi)存頁面的相鄰結(jié)構(gòu)的位。如果沒有相鄰的結(jié)構(gòu)正在使用,就可以回收該頁面。 最后一個方法是實現(xiàn)一個無用單元收集函數(shù)。這個方案依賴于這樣一種情況,即當物理存儲器初次提交時,系統(tǒng)將一個頁面中的所有字節(jié)設(shè)置為0。若要使用該方案,首先必須在你的結(jié)構(gòu)中設(shè)置一個B O O L(也許稱為f I n U s e)。然后,每次你將一個結(jié)構(gòu)放入已提交的內(nèi)存中,必須確保該fIn U s e被置于T R U E。當你的應(yīng)用程序運行時,必須定期調(diào)用無用單元收集函數(shù)。該函數(shù)應(yīng)該遍歷所有潛在的數(shù)據(jù)結(jié)構(gòu)。對于每個數(shù)據(jù)結(jié)構(gòu),該函數(shù)首先要確定是否已經(jīng)為該結(jié)構(gòu)提交內(nèi)存。如果已經(jīng)提交,該函數(shù)將檢查f I n U s e成員,以確定它是否是0。如果該值是0,則表示該結(jié)構(gòu)沒有被使用。如果該值是T R U E,則表示該結(jié)構(gòu)正在使用。當無用單元函數(shù)檢查了屬于既定頁面的所有結(jié)構(gòu)后,如果所有結(jié)構(gòu)都沒有被使用,它將調(diào)用Vi r t u a l F r e e函數(shù),回收該內(nèi)存。當一個結(jié)構(gòu)不再被視為“在用”(In Use)后,就可以立即調(diào)用無用單元收集函數(shù),不過這項操作需要的時間比你想像的要長,因為該函數(shù)要循環(huán)通過所有可能的結(jié)構(gòu)。實現(xiàn)該函數(shù)的一個出色方法是讓它作為低優(yōu)先級線程的一部分來運行。這樣,就不必占用執(zhí)行主應(yīng)用程序的線程的時間。每當主應(yīng)用程序運行空閑時,或者主應(yīng)用程序的線程執(zhí)行文件的I / O操作時,系統(tǒng)就可以給無用單元收集函數(shù)安排運行時間。在上面列出的所有方法中,前面的兩種方法是我個人喜歡使用的方法。不過,如果你的結(jié)構(gòu)比較?。ㄐ∮谝粋€頁面),那么建議你使用最后一種方法。15.5.2 虛擬內(nèi)存分配的示例應(yīng)用程序清單1 5 - 1中列出的V M A l l o c應(yīng)用程序(“15 VMAlloc.exe”)顯示了如何使用虛擬內(nèi)存技術(shù)來處理一個結(jié)構(gòu)數(shù)組。該應(yīng)用程序的源代碼和資源文件均位于本書所附光盤上的1 5 - V M A l l o c目錄下。當啟動該應(yīng)用程序時,將出現(xiàn)圖1 5 - 1所示的窗口。圖15-1 運時VMAlloc 程序時出現(xiàn)的窗口開始時,沒有為該結(jié)構(gòu)數(shù)組保留任何地址空間的區(qū)域,準備為它保留的所有地址空間都是空閑的,如內(nèi)存表所示。當你點擊R e s e r v eR e g i o n(50,2 KB結(jié)構(gòu))按鈕時,V M A l l o c便調(diào)用Vi r t u a l A l l o c函數(shù),保留該區(qū)域,同時內(nèi)存表被更新,以反映該區(qū)域已經(jīng)被保留。當Vi r t u a l A l l o c函數(shù)保留該區(qū)域后,其余按鈕變均為活動按鈕?,F(xiàn)在可以將一個索引鍵入編輯控件,以便選定一個索引,然后單擊U s e按鈕。它的作用是將物理存儲器提交給用于放置數(shù)組元素的內(nèi)存地址。當一個內(nèi)存頁面被提交時,內(nèi)存表被刷新,以反映整個數(shù)組的保留區(qū)域的狀態(tài)。因此,如果該區(qū)域被保留后,你用U s e按鈕將數(shù)組元素7和4 6標記為“在用”,那么該窗口就顯示出圖1 5 - 2所示的樣子(當在4 KB計算機上運行該程序時)。單擊C l e a r按鈕,清除帶有“在用”標記的任何元素。但是這樣做并不回收映射到數(shù)組元素的物理存儲器,因為每個頁面都包含多個結(jié)構(gòu)的空間,清除一個元素并不意味著其他元素也被清除。如果內(nèi)存被回收,那么其他結(jié)構(gòu)中的數(shù)據(jù)就會丟失。由于單擊C l e a r并不影響區(qū)域的物理存儲器,因此當數(shù)組元素被清除時,內(nèi)存表不會被更新。但是,當一個結(jié)構(gòu)被清除時,它的f I n U s e成員將被設(shè)置為FA L S E。這樣的設(shè)置是必要的,因為這使得無用單元收集函數(shù)能夠運行通過所有的結(jié)構(gòu),并回收不再使用的內(nèi)存。如果你現(xiàn)在尚未想到這一點,那么Garbage Collect按鈕會告訴V M A l l o c執(zhí)行它的無用單元收集例程。為了簡化操作,我沒有在各個線程上實現(xiàn)無用單元收集例程。若要展示無用單元收集函數(shù),清除索引4 6上的數(shù)組元素。注意,內(nèi)存表并沒有改變?,F(xiàn)在單擊Garbage Collect按鈕。該程序回收包含元素4 6的內(nèi)存頁面,同時內(nèi)存表被更新,以反映這個變化,如圖1 5 - 3所示。注意, G a r b a g e C o l l e c t函數(shù)可以很容易地用于你自己的應(yīng)用程序。我將它用于對任意大小的數(shù)據(jù)結(jié)構(gòu)數(shù)組的操作,這些結(jié)構(gòu)不必完全等于一個頁面的大小。唯一的要求是,結(jié)構(gòu)的第一個成員的值必須是B O O L,這表示該結(jié)構(gòu)是否處于在用狀態(tài)。圖15-2 標記數(shù)組元素后顯示的窗口圖15-3 內(nèi)存表更新后顯示的窗口最后要說明的是,盡管沒有直觀的圖形為你提供必要的信息,但是,當窗口關(guān)閉時,所有已提交的內(nèi)存均被回收,保留的區(qū)域被釋放。該程序還包含另一個元素尚未加以說明。該程序必須在3個位置上確定區(qū)域的地址空間中的內(nèi)存狀態(tài): 當改變索引后,該程序必須激活U s e按鈕,并停用C l e a r按鈕,或者激活U s e按鈕,停用C l e a r按鈕。 在無用單元收集函數(shù)中,在實際查看是否已經(jīng)設(shè)置f I n U s e標志之前,該程序必須查看內(nèi)存是否已經(jīng)提交。 當更新內(nèi)存表時,該程序必須知道哪個頁面是空閑的,哪個頁面已經(jīng)被保留,哪個頁面已經(jīng)提交。V M A l l o c通過調(diào)用Vi r t u a l Q u e r y函數(shù)來執(zhí)行所有這些測試。清單15-1 VMAlloc示例應(yīng)用程序/*Module: VMAlloc.cppNotices: Copyright (c) 2000 Jeffrey Richter*/#include .CmnHdr.h /* See Appendix A. */#include #include #include Resource.h/ The number of bytes in a page on this host machine.UINT g_uPageSize = 0;/ A dummy data structure used for the array.typedef struct BOOL fInUse; BYTE bOtherData2048 - sizeof(BOOL); SOMEDATA, *PSOMEDATA;/ The number of structures in the array#define MAX_SOMEDATA (50)/ Pointer to an array of data structuresPSOMEDATA g_pSomeData = NULL;/ The rectangular area in the window occupied by the memory mapRECT g_rcMemMap;/BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) chSETDLGICONS(hwnd, IDI_VMALLOC); / Initialize the dialog box by disabling all the nonsetup controls. EnableWindow(GetDlgItem(hwnd, IDC_INDEXTEXT), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_INDEX), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_USE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_CLEAR), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_GARBAGECOLLECT), FALSE); / Get the coordinates of the memory map display. GetWindowRect(GetDlgItem(hwnd, IDC_MEMMAP), &g_rcMemMap); MapWindowPoints(NULL, hwnd, (LPPOINT) &g_rcMemMap, 2); / Destroy the window that identifies the location of the memory map DestroyWindow(GetDlgItem(hwnd, IDC_MEMMAP); / Put the page size in the dialog box just for the users information. TCHAR szBuf10; wsprintf(szBuf, TEXT(%d KB), g_uPageSize / 1024); SetDlgItemText(hwnd, IDC_PAGESIZE, szBuf); / Initialize the edit control. SetDlgItemInt(hwnd, IDC_INDEX, 0, FALSE); return(TRUE);/void Dlg_OnDestroy(HWND hwnd) if (g_pSomeData != NULL) VirtualFree(g_pSomeData, 0, MEM_RELEASE);/VOID GarbageCollect(PVOID pvBase, DWORD dwNum, DWORD dwStructSize) static DWORD s_uPageSize = 0; if (s_uPageSize = 0) / Get the page size used on this CPU. SYSTEM_INFO si; GetSystemInfo(&si); s_uPageSize = si.dwPageSize; UINT uMaxPages = dwNum * dwStructSize / g_uPageSize; for (UINT uPage = 0; uPage uMaxPages; uPage+) BOOL fAnyAllocsInThisPage = FALSE; UINT uIndex = uPage * g_uPageSize / dwStructSize; UINT uIndexLast = uIndex + g_uPageSize / dwStructSize; for (; uIndex uIndexLast; uIndex+) MEMORY_BASIC_INFORMATION mbi; VirtualQuery(&g_pSomeDatauIndex, &mbi, sizeof(mbi); fAnyAllocsInThisPage = (mbi.State = MEM_COMMIT) & * (PBOOL) (PBYTE) pvBase + dwStructSize * uIndex); / Stop checking this page, we know we cant decommit it. if (fAnyAllocsInThisPage) break; if (!fAnyAllocsInThisPage) / No allocated structures in this page; decommit it. VirtualFree(&g_pSomeDatauIndexLast - 1, dwStructSize, MEM_DECOMMIT); / void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) UINT uIndex = 0; switch (id) case IDCANCEL: EndDialog(hwnd, id); break; case IDC_RESERVE: / Reserve enough address space to hold the array of structures. g_pSomeData = (PSOMEDATA) VirtualAlloc(NULL, MAX_SOMEDATA * sizeof(SOMEDATA), MEM_RESERVE, PAGE_READWRITE); / Disable the Reserve button and enable all the other controls. EnableWindow(GetDlgItem(hwnd, IDC_RESERVE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_INDEXTEXT), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_INDEX), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_USE), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_GARBAGECOLLECT), TRUE); / Force the index edit control to have the focus. SetFocus(GetDlgItem(hwnd, IDC_INDEX); / Force the memory map to update InvalidateRect(hwnd, &g_rcMemMap, FALSE); break; case IDC_INDEX: if (codeNotify != EN_CHANGE) break; uIndex = GetDlgItemInt(hwnd, id, NULL, FALSE); if (g_pSomeData != NULL) & chINRANGE(0, uIndex, MAX_SOMEDATA - 1) MEMORY_BASIC_INFORMATION mbi; VirtualQuery(&g_pSomeDatauIndex, &mbi, sizeof(mbi); BOOL fOk = (mbi.State = MEM_COMMIT); if (fOk) fOk = g_pSomeDatauIndex.fInUse; EnableWindow(GetDlgItem(hwnd, IDC_USE), !fOk); EnableWindow(GetDlgItem(hwnd, IDC_CLEAR), fOk); else EnableWindow(GetDlgItem(hwnd, IDC_USE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_CLEAR), FALSE); break; case IDC_USE: uIndex = GetDlgItemInt(hwnd, IDC_INDEX, NULL, FALSE); if (chINRANGE(0, uIndex, MAX_SOMEDATA - 1) / NOTE: New pages are always zeroed by the system VirtualAlloc(&g_pSomeDatauIndex, sizeof(SOMEDATA), MEM_COMMIT, PAGE_READWRITE); g_pSomeDatauIndex.fInUse = TRUE; Ena
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 世界愛牙日活動方案
- 業(yè)主親情活動方案
- 業(yè)主搬家活動策劃方案
- 業(yè)主首屆籃球賽活動方案
- 大規(guī)模招聘活動方案
- 大學教師讀書節(jié)活動方案
- 大牌小樣開業(yè)活動方案
- 大眾公司策劃方案
- 夏季水杯專場活動方案
- 大班唱歌活動方案
- JBT 11699-2013 高處作業(yè)吊籃安裝、拆卸、使用技術(shù)規(guī)程
- 家長會課件:初中七年級家長會課件
- 廈門大學2023年826物理化學考研真題(含答案)
- 市政工程施工安全臺帳范本12本(含內(nèi)容)
- 《食管胃底靜脈曲張》課件
- 周圍性面癱的分期治療技術(shù)課件
- AutoCAD 2020中文版從入門到精通(標準版)
- 初中英語2023年中考專題訓練任務(wù)型閱讀-判斷正誤篇
- 2022年江西南昌高新技術(shù)產(chǎn)業(yè)開發(fā)區(qū)人民檢察院聘用制檢察輔助人員招聘考試真題
- 小學安全隱患排查表
- 學校財務(wù)人員述職報告范文
評論
0/150
提交評論