




已閱讀5頁(yè),還剩33頁(yè)未讀, 繼續(xù)免費(fèi)閱讀
版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第15章 在應(yīng)用程序中使用虛擬內(nèi)存Wi n d o w s提供了3種進(jìn)行內(nèi)存管理的方法,它們是: 虛擬內(nèi)存,最適合用來(lái)管理大型對(duì)象或結(jié)構(gòu)數(shù)組。 內(nèi)存映射文件,最適合用來(lái)管理大型數(shù)據(jù)流(通常來(lái)自文件)以及在單個(gè)計(jì)算機(jī)上運(yùn)行的多個(gè)進(jìn)程之間共享數(shù)據(jù)。 內(nèi)存堆棧,最適合用來(lái)管理大量的小對(duì)象。本章將要介紹第一種方法,即虛擬內(nèi)存。內(nèi)存映射文件和堆棧分別在第1 7章和第1 8章介紹。用于管理虛擬內(nèi)存的函數(shù)可以用來(lái)直接保留一個(gè)地址空間區(qū)域,將物理存儲(chǔ)器(來(lái)自頁(yè)文件)提交給該區(qū)域,并且可以設(shè)置你自己的保護(hù)屬性。15.1 在地址空間中保留一個(gè)區(qū)域通過(guò)調(diào)用Vi r t u a l A l l o c函數(shù),可以在進(jìn)程的地址空間中保留一個(gè)區(qū)域:PVOID VirtualAlloc( PVOID pvAddress, SIZE_T dwSize, DWORD fdwAllocationType, DWORD fdwProtect);第一個(gè)參數(shù)p v A d d r e s s包含一個(gè)內(nèi)存地址,用于設(shè)定想讓系統(tǒng)將地址空間保留在什么地方。在大多數(shù)情況下,你為該參數(shù)傳遞M U L L。它告訴Vi r t u a l A l l o c,保存著一個(gè)空閑地址區(qū)域的記錄的系統(tǒng)應(yīng)該將區(qū)域保留在它認(rèn)為合適的任何地方。系統(tǒng)可以從進(jìn)程的地址空間的任何位置來(lái)保留一個(gè)區(qū)域,因?yàn)椴荒鼙WC系統(tǒng)可以從地址空間的底部向上或者從上面向底部來(lái)分配各個(gè)區(qū)域??梢允褂肕 E M _ TO P _ D O W N標(biāo)志來(lái)說(shuō)明該分配方式。這個(gè)標(biāo)志將在本章的后面加以介紹。對(duì)大多數(shù)程序員來(lái)說(shuō),能夠選擇一個(gè)特定的內(nèi)存地址,并在該地址保留一個(gè)區(qū)域,這是個(gè)非同尋常的想法。當(dāng)你在過(guò)去分配內(nèi)存時(shí),操作系統(tǒng)只是尋找一個(gè)其大小足以滿(mǎn)足需要的內(nèi)存塊,并分配該內(nèi)存塊,然后返回它的地址。但是,由于每個(gè)進(jìn)程有它自己的地址空間,因此可以設(shè)定一個(gè)基本內(nèi)存地址,在這個(gè)地址上讓操作系統(tǒng)保留地址空間區(qū)域。例如,你想將一個(gè)從50 MB開(kāi)始的區(qū)域保留在進(jìn)程的地址空間中。這時(shí),可以傳遞52 428 800(5 01 0 2 41 0 2 4)作為p v A d d r e s s參數(shù)。如果該內(nèi)存地址有一個(gè)足夠大的空閑區(qū)域滿(mǎn)足你的要求,那么系統(tǒng)就保留這個(gè)區(qū)域并返回。如果在特定的地址上不存在空閑區(qū)域,或者如果空閑區(qū)域不夠大,那么系統(tǒng)就不能滿(mǎn)足你的要求,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ù)傳遞的任何地址必須始終位于進(jìn)程的用戶(hù)方式分區(qū)中,否則對(duì)Vi r t u a l A l l o c函數(shù)的調(diào)用就會(huì)失敗,導(dǎo)致它返回N U L L。第1 3章講過(guò),地址空間區(qū)域總是按照分配粒度的邊界來(lái)保留的(迄今為止在所有的Wi n d o w s環(huán)境下均是6 4 K B )。因此,如果試圖在進(jìn)程地址空間中保留一個(gè)從19 668 992(30065 536 + 8192)這個(gè)地址開(kāi)始的區(qū)域,系統(tǒng)就會(huì)將這個(gè)地址圓整為6 4 K B的倍數(shù),然后保留從19 660 800(3 0 065 536)這個(gè)地址開(kāi)始的區(qū)域。如果Vi r t u a l A l l o c函數(shù)能夠滿(mǎn)足你的要求,那么它就返回一個(gè)值,指明保留區(qū)域的基地址。如果傳遞一個(gè)特定的地址作為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的值相同,并被圓整為(如果需要的話(huà)) 6 4 K B邊界值。Vi r t u a l A l l o c函數(shù)的第二個(gè)參數(shù)是d w S i z e,用于設(shè)定想保留的區(qū)域的大小(以字節(jié)為計(jì)量單位)。由于系統(tǒng)保留的區(qū)域始終必須是C P U頁(yè)面大小的倍數(shù),因此,如果試圖保留一個(gè)跨越6 2 K B的區(qū)域,結(jié)果就會(huì)在使用4 KB、8 KB或16 KB頁(yè)面的計(jì)算機(jī)上產(chǎn)生一個(gè)跨越6 4 K B的區(qū)域。Vi r t u a l A l l o c函數(shù)的第三個(gè)參數(shù)是f d w A l l o c a t i o n Ty p e,它能夠告訴系統(tǒng)你想保留一個(gè)區(qū)域還是提交物理存儲(chǔ)器(這樣的區(qū)分是必要的,因?yàn)閂i r t u a l A l l o c函數(shù)也可以用來(lái)提交物理存儲(chǔ)器)。若要保留一個(gè)地址空間區(qū)域,必須傳遞M E M _ R E S E RV E標(biāo)識(shí)符作為F d w A l l o c a t i o n Ty p e參數(shù)的值。如果保留的區(qū)域預(yù)計(jì)在很長(zhǎng)時(shí)間內(nèi)不會(huì)被釋放,那么可以在盡可能高的內(nèi)存地址上保留該區(qū)域。這樣,該區(qū)域就不會(huì)從進(jìn)程地址空間的中間位置上進(jìn)行保留。因?yàn)樵谶@個(gè)位置上它可能導(dǎo)致區(qū)域分成碎片。如果想讓系統(tǒng)在最高內(nèi)存地址上保留一個(gè)區(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)志連接起來(lái)。注意在Windows 98下,M E M _ TO P _ D O W N標(biāo)志將被忽略。最后一個(gè)參數(shù)是f d w P r o t e c t,用于指明應(yīng)該賦予該地址空間區(qū)域的保護(hù)屬性。與該區(qū)域相關(guān)聯(lián)的保護(hù)屬性對(duì)映射到該區(qū)域的已提交內(nèi)存沒(méi)有影響。無(wú)論賦予區(qū)域的保護(hù)屬性是什么,如果沒(méi)有提交任何物理存儲(chǔ)器,那么訪(fǎng)問(wèn)該范圍中的內(nèi)存地址的任何企圖都將導(dǎo)致該線(xiàn)程引發(fā)一個(gè)訪(fǎng)問(wèn)違規(guī)。當(dāng)保留一個(gè)區(qū)域時(shí),應(yīng)該為該區(qū)域賦予一個(gè)已提交內(nèi)存最常用的保護(hù)屬性。例如,如果打算提交的物理存儲(chǔ)器的保護(hù)屬性是PA G E _ R E A D W R I T E(這是最常用的保護(hù)屬性),那么應(yīng)該用PA G E _ R E A D W R I T E保護(hù)屬性來(lái)保留該區(qū)域。當(dāng)區(qū)域的保護(hù)屬性與已提交內(nèi)存的保護(hù)屬性相匹配時(shí),系統(tǒng)保存的內(nèi)部記錄的運(yùn)行效率最高??梢允褂孟铝斜Wo(hù)屬性中的任何一個(gè): 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ū)域時(shí),不能使用保護(hù)屬性標(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保護(hù)屬性。如果試圖保留使用PA G E _ E X E C U T E或PA G E _ E X E C U T E _ R E A D兩個(gè)保護(hù)屬性的區(qū)域,將會(huì)產(chǎn)生一個(gè)帶有PA G E _ R E A D O N LY保護(hù)屬性的區(qū)域。同樣,如果保留一個(gè)使用PA G E _ E X E C U T E _ R E A D W R I T E保護(hù)屬性的區(qū)域,就會(huì)產(chǎn)生一個(gè)帶有PA G E _ R E A D W R I T E保護(hù)屬性的區(qū)域。15.2 在保留區(qū)域中的提交存儲(chǔ)器當(dāng)保留一個(gè)區(qū)域后,必須將物理存儲(chǔ)器提交給該區(qū)域,然后才能訪(fǎng)問(wèn)該區(qū)域中包含的內(nèi)存地址。系統(tǒng)從它的頁(yè)文件中將已提交的物理存儲(chǔ)器分配給一個(gè)區(qū)域。物理存儲(chǔ)器總是按頁(yè)面邊界和頁(yè)面大小的塊來(lái)提交的。若要提交物理存儲(chǔ)器,必須再次調(diào)用Vi r t u a l A l l o c函數(shù)。不過(guò)這次為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)志。傳遞的頁(yè)面保護(hù)屬性通常與調(diào)用Vi r t u a l A l l o c來(lái)保留區(qū)域時(shí)使用的保護(hù)屬性相同(大多數(shù)情況下是PA G E _ R E A D W R I T E),不過(guò)也可以設(shè)定一個(gè)不同的保護(hù)屬性。在已保留的區(qū)域中,你必須告訴Vi r t u a l A l l o c函數(shù),你想將物理存儲(chǔ)器提交到何處,以及要提交多少物理存儲(chǔ)器。為了做到這一點(diǎn),可以在p v A d d r e s s參數(shù)中設(shè)定你需要的內(nèi)存地址,并在d w S i z e參數(shù)中設(shè)定物理存儲(chǔ)器的數(shù)量(以字節(jié)為計(jì)量單位)。注意,不必立即將物理存儲(chǔ)器提交給整個(gè)區(qū)域。下面讓我們來(lái)看一個(gè)如何提交物理存儲(chǔ)器。比如說(shuō),你的應(yīng)用程序是在x86 CPU上運(yùn)行的,該應(yīng)用程序保留了一個(gè)從地址5 242 880開(kāi)始的512 KB的區(qū)域。你想讓?xiě)?yīng)用程序?qū)⑽锢泶鎯?chǔ)器提交給已保留區(qū)域的6 KB部分,從2 KB的地方開(kāi)始,直到已保留區(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ù),如下所示:VirtualAlloc(PVOID)(5242880 + (2 * 1024), 6 * 1024, MEM_COMMIT, PAGE_READWRITE);在這個(gè)例子中,系統(tǒng)必須提交8 KB的物理存儲(chǔ)器,地址范圍從5 242 880到5 251 071 (5 242 880 + 8 KB -1字節(jié))。這兩個(gè)提交的頁(yè)面都擁有PA G E _ R E A D W R I T E保護(hù)屬性。保護(hù)屬性只以整個(gè)頁(yè)面為單位來(lái)賦予。同一個(gè)內(nèi)存頁(yè)面的不同部分不能使用不同的保護(hù)屬性。然而,區(qū)域中的一個(gè)頁(yè)面可以使用一種保護(hù)屬性(比如PA G E _ R E A D W R I T E),而同一個(gè)區(qū)域中的另一個(gè)頁(yè)面可以使用不同的保護(hù)屬性(比如PA G E _ R E A D O N LY)。15.3 同時(shí)進(jìn)行區(qū)域的保留和內(nèi)存的提交有時(shí)你可能想要在保留區(qū)域的同時(shí),將物理存儲(chǔ)器提交給它。只需要一次調(diào)用Vi r t u a l A l l o c函數(shù)就能進(jìn)行這樣的操作,如下所示:PVOID pvMem = VirtualAlloc(NULL, 99 * 1024, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);這個(gè)函數(shù)調(diào)用請(qǐng)求保留一個(gè)99 KB的區(qū)域,并且將99 KB的物理存儲(chǔ)器提交給它。當(dāng)系統(tǒng)處理這個(gè)函數(shù)調(diào)用時(shí),它首先要搜索你的進(jìn)程的地址空間,找出未保留的地址空間中一個(gè)地址連續(xù)的區(qū)域,它必須足夠大,能夠存放100 KB(在4 KB頁(yè)面的計(jì)算機(jī)上)或104 KB(在8 KB頁(yè)面的計(jì)算機(jī)上)。系統(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,如果能夠保留一個(gè)合適的區(qū)域,系統(tǒng)就將物理存儲(chǔ)器提交給整個(gè)區(qū)域。無(wú)論是該區(qū)域還是提交的內(nèi)存,都將被賦予PA G E _ R E A D W R I T E保護(hù)屬性。最后需要說(shuō)明的是,Vi r t u a l A l l o c將返回保留區(qū)域和提交區(qū)域的虛擬地址,然后該虛擬地址被保存在p v M e m變量中。如果系統(tǒng)無(wú)法找到足夠大的地址空間,或者不能提交該物理存儲(chǔ)器,Vi r t u a l A l l o c將返回N U L L。當(dāng)用這種方式來(lái)保留一個(gè)區(qū)域和提交物理存儲(chǔ)器時(shí),將特定的地址作為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ù)連接起來(lái),并為p v A d d r e s s參數(shù)傳遞N U L L,讓系統(tǒng)在進(jìn)程的地址空間的頂部選定一個(gè)適當(dāng)?shù)膮^(qū)域。15.4 何時(shí)提交物理存儲(chǔ)器假設(shè)想實(shí)現(xiàn)一個(gè)電子表格應(yīng)用程序,這個(gè)電子表格為2 0 0行x 256列。對(duì)于每一個(gè)單元格,都需要一個(gè)C E L L D ATA結(jié)構(gòu)來(lái)描述單元格的內(nèi)容。若要處理這種二維單元格矩陣,最容易的方法是在應(yīng)用程序中聲明下面的變量:CELLDATA CellData200256;如果C E L L D ATA結(jié)構(gòu)的大小是1 2 8字節(jié),那么這個(gè)二維矩陣將需要6 553 600(200 x 256 x1 2 8)個(gè)字節(jié)的物理存儲(chǔ)器。對(duì)于電子表格來(lái)說(shuō),如果直接用頁(yè)文件來(lái)分配物理存儲(chǔ)器,那么這是個(gè)不小的數(shù)目了,尤其是考慮到大多數(shù)用戶(hù)只是將信息放入少數(shù)的單元格中,而大部分單元格卻空閑不用,因此顯得有些浪費(fèi)。內(nèi)存的利用率非常低。傳統(tǒng)上,電子表格一直是用其他數(shù)據(jù)結(jié)構(gòu)技術(shù)來(lái)實(shí)現(xiàn)的,比如鏈接表等。使用鏈接表,只需要為電子表格中實(shí)際包含數(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ù)先聲明二維矩陣和實(shí)現(xiàn)鏈接表的兩全其美的方法。運(yùn)用虛擬內(nèi)存,既可以使用已聲明的矩陣技術(shù)進(jìn)行快速而方便的訪(fǎng)問(wèn),又可以利用鏈接表技術(shù)大大節(jié)省內(nèi)存的使用量。如果想利用虛擬內(nèi)存技術(shù)的優(yōu)點(diǎn),你的程序必須按照下列步驟來(lái)編寫(xiě):1) 保留一個(gè)足夠大的地址空間區(qū)域,用來(lái)存放C E L L D ATA結(jié)構(gòu)的整個(gè)數(shù)組。保留一個(gè)根本不使用任何物理存儲(chǔ)器的區(qū)域。2) 當(dāng)用戶(hù)將數(shù)據(jù)輸入一個(gè)單元格時(shí),找出C E L L D ATA結(jié)構(gòu)應(yīng)該進(jìn)入的保留區(qū)域中的內(nèi)存地址。當(dāng)然,這時(shí)尚未有任何物理存儲(chǔ)器被映射到該地址,因此,訪(fǎng)問(wèn)該地址的內(nèi)存的任何企圖都會(huì)引發(fā)訪(fǎng)問(wèn)違規(guī)。3) 就C E L L D ATA結(jié)構(gòu)來(lái)說(shuō),只將足夠的物理存儲(chǔ)器提交給第二步中找到的內(nèi)存地址(你可以告訴系統(tǒng)將物理存儲(chǔ)器提交給保留區(qū)域的特定部分,這個(gè)區(qū)域既可以包含映射到物理存儲(chǔ)器的各個(gè)部分,也可以包含沒(méi)有映射到物理存儲(chǔ)器的各個(gè)部分)。4) 設(shè)置新的C E L L D ATA結(jié)構(gòu)的成員?,F(xiàn)在物理存儲(chǔ)器已經(jīng)映射到相應(yīng)的位置,你的程序能夠訪(fǎng)問(wèn)內(nèi)存,而不會(huì)引發(fā)訪(fǎng)問(wèn)違規(guī)。這個(gè)虛擬內(nèi)存技術(shù)非常出色,因?yàn)橹挥性谟脩?hù)將數(shù)據(jù)輸入電子表格的單元格時(shí),才會(huì)提交物理存儲(chǔ)器。由于電子表格中的大多數(shù)單元格是空的,因此大部分保留區(qū)域沒(méi)有提交給它的物理存儲(chǔ)器。虛擬內(nèi)存技術(shù)存在的一個(gè)問(wèn)題是,必須確定物理存儲(chǔ)器在何時(shí)提交。如果用戶(hù)將數(shù)據(jù)輸入一個(gè)單元格,然后只是編輯或修改該數(shù)據(jù),那么就沒(méi)有必要提交物理存儲(chǔ)器,因?yàn)樵搯卧竦腃 E L L D ATA結(jié)構(gòu)的內(nèi)存在數(shù)據(jù)初次輸入時(shí)就已經(jīng)提交了。另外,系統(tǒng)總是按頁(yè)面的分配粒度來(lái)提交物理存儲(chǔ)器的。因此,當(dāng)試圖為單個(gè)C E L L D ATA結(jié)構(gòu)提交物理存儲(chǔ)器時(shí)(像上面的第二步那樣),系統(tǒng)實(shí)際上提交的是內(nèi)存的一個(gè)完整的頁(yè)面。這并不像它聽(tīng)起來(lái)那樣十分浪費(fèi):為單個(gè)C E L L D ATA結(jié)構(gòu)提交物理存儲(chǔ)器的結(jié)果是,也要為附近的其他C E L L D ATA結(jié)構(gòu)提交內(nèi)存。如果這時(shí)用戶(hù)將數(shù)據(jù)輸入鄰近的單元格(這是經(jīng)常出現(xiàn)的情況),就不需要提交更多的物理存儲(chǔ)器。有4種方法可以用來(lái)確定是否要將物理存儲(chǔ)器提交給區(qū)域的一個(gè)部分: 始終設(shè)法進(jìn)行物理存儲(chǔ)器的提交。每次調(diào)用Vi r t u a l A l l o c函數(shù)的時(shí)候,不要查看物理存儲(chǔ)器是否已經(jīng)映射到地址空間區(qū)域的一個(gè)部分,而是讓你的程序設(shè)法進(jìn)行內(nèi)存的提交。系統(tǒng)首先查看內(nèi)存是否已經(jīng)被提交,如果已經(jīng)提交,那么就不要提交更多的物理存儲(chǔ)器。這種方法最容易操作,但是它的缺點(diǎn)是每次改變C E L L D ATA結(jié)構(gòu)時(shí)要多進(jìn)行一次函數(shù)的調(diào)用,這會(huì)使程序運(yùn)行得比較慢。 (使用Vi r t u a l Q u e r y函數(shù))確定物理存儲(chǔ)器是否已經(jīng)提交給包含C E L L D ATA結(jié)構(gòu)的地址空間。如果已經(jīng)提交了,那么就不要進(jìn)行任何別的操作。如果尚未提交,則可以調(diào)用Vi r t u a l A l l o c函數(shù)以便提交內(nèi)存。這種方法實(shí)際上比第一種方法差,它既會(huì)增加代碼的長(zhǎng)度,又會(huì)降低程序運(yùn)行的速度(因?yàn)樵黾恿藢?duì)Vi r t u a l A l l o c函數(shù)的調(diào)用)。 保留一個(gè)關(guān)于哪些頁(yè)面已經(jīng)提交和哪些頁(yè)面尚未提交的記錄。這樣做可以使你的應(yīng)用程序運(yùn)行得更快,因?yàn)椴槐卣{(diào)用Vi r t u a l A l l o c函數(shù),你的代碼能夠比系統(tǒng)更快地確定內(nèi)存是否已經(jīng)被提交。它的缺點(diǎn)是,必須不斷跟蹤頁(yè)面提交的信息,這可能非常簡(jiǎn)單,也可能非常困難,要根據(jù)你的情況而定。 使用結(jié)構(gòu)化異常處理( S E H)方法,這是最好的方法。S E H是一個(gè)操作系統(tǒng)特性,它使系統(tǒng)能夠在發(fā)生某種情況時(shí)將此情況通知你的應(yīng)用程序。實(shí)際上可以創(chuàng)建一個(gè)帶有異常處理程序的應(yīng)用程序,然后,每當(dāng)試圖訪(fǎng)問(wèn)未提交的內(nèi)存時(shí),系統(tǒng)就將這個(gè)問(wèn)題通知應(yīng)用程序。然后你的應(yīng)用程序便進(jìn)行內(nèi)存的提交,并告訴系統(tǒng)重新運(yùn)行導(dǎo)致異常條件的指令。這時(shí)對(duì)內(nèi)存的訪(fǎng)問(wèn)就能成功地進(jìn)行了,程序?qū)⒗^續(xù)運(yùn)行,仿佛從未發(fā)生過(guò)問(wèn)題一樣。這種方法是優(yōu)點(diǎn)最多的方法,因?yàn)樾枰龅墓ぷ髯钌伲ㄒ簿褪钦f(shuō)要你編寫(xiě)的代碼比較少),同時(shí),你的程序可以全速運(yùn)行。關(guān)于S E H的全面介紹,請(qǐng)參見(jiàn)第2 3、2 4和2 5章。第2 5章中的電子表格示例應(yīng)用程序說(shuō)明了如何按照上面介紹的方法來(lái)使用虛擬內(nèi)存。15.5 回收虛擬內(nèi)存和釋放地址空間區(qū)域若要回收映射到一個(gè)區(qū)域的物理存儲(chǔ)器,或者釋放這個(gè)地址空間區(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ù)來(lái)釋放一個(gè)已保留區(qū)域的簡(jiǎn)單例子。當(dāng)你的進(jìn)程不再訪(fǎng)問(wèn)區(qū)域中的物理存儲(chǔ)器時(shí),就可以釋放整個(gè)保留的區(qū)域和所有提交給該區(qū)域的物理存儲(chǔ)器,方法是一次調(diào)用Vi r t u a l F r e e函數(shù)。就這個(gè)函數(shù)的調(diào)用來(lái)說(shuō), p v A d d r e s s參數(shù)必須是該區(qū)域的基地址。此地址與該區(qū)域被保留時(shí)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。實(shí)際上,必須為d w S i z e參數(shù)傳遞0,否則對(duì)Vi r t u a l F r e e的調(diào)用就會(huì)失敗。對(duì)于第三個(gè)參數(shù)f d w F r e e Ty p e,必須傳遞M E M _ R E L E A S E,以告訴系統(tǒng)將所有映射的物理存儲(chǔ)器提交給該區(qū)域并釋放該區(qū)域。當(dāng)釋放一個(gè)區(qū)域時(shí),必須釋放該區(qū)域保留的所有地址空間。例如不能保留一個(gè)128 KB的區(qū)域,然后決定只釋放它的64 KB。必須釋放所有的128 KB。當(dāng)想要從一個(gè)區(qū)域回收某些物理存儲(chǔ)器,但是卻不釋放該區(qū)域時(shí),也可以調(diào)用Vi r t u a l F r e e函數(shù),若要回收某些物理存儲(chǔ)器,必須在Vi r t u a l F r e e函數(shù)的p v A d d r e s s參數(shù)中傳遞用于標(biāo)識(shí)要回收的第一個(gè)頁(yè)面的內(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)志。與提交物理存儲(chǔ)器的情況一樣,回收時(shí)也必須按照頁(yè)面的分配粒度來(lái)進(jìn)行。這就是說(shuō),設(shè)定頁(yè)面中間的一個(gè)內(nèi)存地址就可以回收整個(gè)頁(yè)面。當(dāng)然,如果pvAddress + dwSize的值位于一個(gè)頁(yè)面的中間,那么包含該地址的整個(gè)頁(yè)面將被回收。因此位于pvAddress 至pvAddress +d w S i z e范圍內(nèi)的所有頁(yè)面均被回收。如果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)的已分配頁(yè)面。當(dāng)物理存儲(chǔ)器的頁(yè)面已經(jīng)回收之后,已釋放的物理存儲(chǔ)器就可以供系統(tǒng)中的所有其他進(jìn)程使用,如果試圖訪(fǎng)問(wèn)未回收的內(nèi)存,將會(huì)造成訪(fǎng)問(wèn)違規(guī)。15.5.1 何時(shí)回收物理存儲(chǔ)器在實(shí)踐中,知道何時(shí)回收內(nèi)存是非常困難的。讓我們?cè)僖噪娮颖砀駷槔H绻愕膽?yīng)用程序是在x 8 6計(jì)算機(jī)上運(yùn)行,每個(gè)內(nèi)存頁(yè)面是4 KB ,它可以存放3 2個(gè)(4 0 9 6 / 1 2 8)C E L L D ATA結(jié)構(gòu)。如果用戶(hù)刪除了單元格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)存頁(yè)面。那么怎么能夠知道這個(gè)情況呢?可以用下面3種方法來(lái)解決這個(gè)問(wèn)題。 毫無(wú)疑問(wèn),最容易的方法是設(shè)計(jì)一個(gè)C E L L D ATA結(jié)構(gòu),它的大小只有一個(gè)頁(yè)面。這時(shí),由于始終都是每個(gè)頁(yè)面使用一個(gè)結(jié)構(gòu),因此當(dāng)不再需要該結(jié)構(gòu)中的數(shù)據(jù)時(shí),就可以回收該頁(yè)面的物理存儲(chǔ)器。即使你的數(shù)據(jù)結(jié)構(gòu)是x86 CPU上的8 KB或12 KB頁(yè)面的倍數(shù)(通常這是非常大的數(shù)據(jù)結(jié)構(gòu)),回收內(nèi)存仍然是非常容易的。當(dāng)然,如果要使用這種方法,必須定義你的數(shù)據(jù)結(jié)構(gòu),使之符合你針對(duì)的C P U的頁(yè)面大小而不是我們通常編寫(xiě)程序所用的結(jié)構(gòu)。 更為實(shí)用的方法是保留一個(gè)正在使用的結(jié)構(gòu)的記錄。為了節(jié)省內(nèi)存,可以使用一個(gè)位圖。這樣,如果有一個(gè)1 0 0個(gè)結(jié)構(gòu)的數(shù)組,你也可以維護(hù)一個(gè)1 0 0位的數(shù)組。開(kāi)始時(shí),所有的位均設(shè)置為0,表示這些結(jié)構(gòu)都沒(méi)有使用。當(dāng)使用這些結(jié)構(gòu)時(shí),可以將對(duì)應(yīng)的位設(shè)置為1。然后,每當(dāng)不需要某個(gè)結(jié)構(gòu),并將它的位重新改為0時(shí),你可以檢查屬于同一個(gè)內(nèi)存頁(yè)面的相鄰結(jié)構(gòu)的位。如果沒(méi)有相鄰的結(jié)構(gòu)正在使用,就可以回收該頁(yè)面。 最后一個(gè)方法是實(shí)現(xiàn)一個(gè)無(wú)用單元收集函數(shù)。這個(gè)方案依賴(lài)于這樣一種情況,即當(dāng)物理存儲(chǔ)器初次提交時(shí),系統(tǒng)將一個(gè)頁(yè)面中的所有字節(jié)設(shè)置為0。若要使用該方案,首先必須在你的結(jié)構(gòu)中設(shè)置一個(gè)B O O L(也許稱(chēng)為f I n U s e)。然后,每次你將一個(gè)結(jié)構(gòu)放入已提交的內(nèi)存中,必須確保該fIn U s e被置于T R U E。當(dāng)你的應(yīng)用程序運(yùn)行時(shí),必須定期調(diào)用無(wú)用單元收集函數(shù)。該函數(shù)應(yīng)該遍歷所有潛在的數(shù)據(jù)結(jié)構(gòu)。對(duì)于每個(gè)數(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)沒(méi)有被使用。如果該值是T R U E,則表示該結(jié)構(gòu)正在使用。當(dāng)無(wú)用單元函數(shù)檢查了屬于既定頁(yè)面的所有結(jié)構(gòu)后,如果所有結(jié)構(gòu)都沒(méi)有被使用,它將調(diào)用Vi r t u a l F r e e函數(shù),回收該內(nèi)存。當(dāng)一個(gè)結(jié)構(gòu)不再被視為“在用”(In Use)后,就可以立即調(diào)用無(wú)用單元收集函數(shù),不過(guò)這項(xiàng)操作需要的時(shí)間比你想像的要長(zhǎng),因?yàn)樵摵瘮?shù)要循環(huán)通過(guò)所有可能的結(jié)構(gòu)。實(shí)現(xiàn)該函數(shù)的一個(gè)出色方法是讓它作為低優(yōu)先級(jí)線(xiàn)程的一部分來(lái)運(yùn)行。這樣,就不必占用執(zhí)行主應(yīng)用程序的線(xiàn)程的時(shí)間。每當(dāng)主應(yīng)用程序運(yùn)行空閑時(shí),或者主應(yīng)用程序的線(xiàn)程執(zhí)行文件的I / O操作時(shí),系統(tǒng)就可以給無(wú)用單元收集函數(shù)安排運(yùn)行時(shí)間。在上面列出的所有方法中,前面的兩種方法是我個(gè)人喜歡使用的方法。不過(guò),如果你的結(jié)構(gòu)比較?。ㄐ∮谝粋€(gè)頁(yè)面),那么建議你使用最后一種方法。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ù)來(lái)處理一個(gè)結(jié)構(gòu)數(shù)組。該應(yīng)用程序的源代碼和資源文件均位于本書(shū)所附光盤(pán)上的1 5 - V M A l l o c目錄下。當(dāng)啟動(dòng)該應(yīng)用程序時(shí),將出現(xiàn)圖1 5 - 1所示的窗口。圖15-1 運(yùn)時(shí)VMAlloc 程序時(shí)出現(xiàn)的窗口開(kāi)始時(shí),沒(méi)有為該結(jié)構(gòu)數(shù)組保留任何地址空間的區(qū)域,準(zhǔn)備為它保留的所有地址空間都是空閑的,如內(nèi)存表所示。當(dāng)你點(diǎn)擊R e s e r v eR e g i o n(50,2 KB結(jié)構(gòu))按鈕時(shí),V M A l l o c便調(diào)用Vi r t u a l A l l o c函數(shù),保留該區(qū)域,同時(shí)內(nèi)存表被更新,以反映該區(qū)域已經(jīng)被保留。當(dāng)Vi r t u a l A l l o c函數(shù)保留該區(qū)域后,其余按鈕變均為活動(dòng)按鈕?,F(xiàn)在可以將一個(gè)索引鍵入編輯控件,以便選定一個(gè)索引,然后單擊U s e按鈕。它的作用是將物理存儲(chǔ)器提交給用于放置數(shù)組元素的內(nèi)存地址。當(dāng)一個(gè)內(nèi)存頁(yè)面被提交時(shí),內(nèi)存表被刷新,以反映整個(gè)數(shù)組的保留區(qū)域的狀態(tài)。因此,如果該區(qū)域被保留后,你用U s e按鈕將數(shù)組元素7和4 6標(biāo)記為“在用”,那么該窗口就顯示出圖1 5 - 2所示的樣子(當(dāng)在4 KB計(jì)算機(jī)上運(yùn)行該程序時(shí))。單擊C l e a r按鈕,清除帶有“在用”標(biāo)記的任何元素。但是這樣做并不回收映射到數(shù)組元素的物理存儲(chǔ)器,因?yàn)槊總€(gè)頁(yè)面都包含多個(gè)結(jié)構(gòu)的空間,清除一個(gè)元素并不意味著其他元素也被清除。如果內(nèi)存被回收,那么其他結(jié)構(gòu)中的數(shù)據(jù)就會(huì)丟失。由于單擊C l e a r并不影響區(qū)域的物理存儲(chǔ)器,因此當(dāng)數(shù)組元素被清除時(shí),內(nèi)存表不會(huì)被更新。但是,當(dāng)一個(gè)結(jié)構(gòu)被清除時(shí),它的f I n U s e成員將被設(shè)置為FA L S E。這樣的設(shè)置是必要的,因?yàn)檫@使得無(wú)用單元收集函數(shù)能夠運(yùn)行通過(guò)所有的結(jié)構(gòu),并回收不再使用的內(nèi)存。如果你現(xiàn)在尚未想到這一點(diǎn),那么Garbage Collect按鈕會(huì)告訴V M A l l o c執(zhí)行它的無(wú)用單元收集例程。為了簡(jiǎn)化操作,我沒(méi)有在各個(gè)線(xiàn)程上實(shí)現(xiàn)無(wú)用單元收集例程。若要展示無(wú)用單元收集函數(shù),清除索引4 6上的數(shù)組元素。注意,內(nèi)存表并沒(méi)有改變?,F(xiàn)在單擊Garbage Collect按鈕。該程序回收包含元素4 6的內(nèi)存頁(yè)面,同時(shí)內(nèi)存表被更新,以反映這個(gè)變化,如圖1 5 - 3所示。注意, G a r b a g e C o l l e c t函數(shù)可以很容易地用于你自己的應(yīng)用程序。我將它用于對(duì)任意大小的數(shù)據(jù)結(jié)構(gòu)數(shù)組的操作,這些結(jié)構(gòu)不必完全等于一個(gè)頁(yè)面的大小。唯一的要求是,結(jié)構(gòu)的第一個(gè)成員的值必須是B O O L,這表示該結(jié)構(gòu)是否處于在用狀態(tài)。圖15-2 標(biāo)記數(shù)組元素后顯示的窗口圖15-3 內(nèi)存表更新后顯示的窗口最后要說(shuō)明的是,盡管沒(méi)有直觀的圖形為你提供必要的信息,但是,當(dāng)窗口關(guān)閉時(shí),所有已提交的內(nèi)存均被回收,保留的區(qū)域被釋放。該程序還包含另一個(gè)元素尚未加以說(shuō)明。該程序必須在3個(gè)位置上確定區(qū)域的地址空間中的內(nèi)存狀態(tài): 當(dāng)改變索引后,該程序必須激活U s e按鈕,并停用C l e a r按鈕,或者激活U s e按鈕,停用C l e a r按鈕。 在無(wú)用單元收集函數(shù)中,在實(shí)際查看是否已經(jīng)設(shè)置f I n U s e標(biāo)志之前,該程序必須查看內(nèi)存是否已經(jīng)提交。 當(dāng)更新內(nèi)存表時(shí),該程序必須知道哪個(gè)頁(yè)面是空閑的,哪個(gè)頁(yè)面已經(jīng)被保留,哪個(gè)頁(yè)面已經(jīng)提交。V M A l l o c通過(guò)調(diào)用Vi r t u a l Q u e r y函數(shù)來(lái)執(zhí)行所有這些測(cè)試。清單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. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025至2030年中國(guó)滾動(dòng)式烤香腸機(jī)行業(yè)投資前景及策略咨詢(xún)研究報(bào)告
- 2025年鋼管項(xiàng)目可行性報(bào)告
- 環(huán)保型施工技術(shù)與噪音減少行動(dòng)計(jì)劃
- 國(guó)際交流項(xiàng)目語(yǔ)言輔導(dǎo)計(jì)劃
- 中式烹調(diào)師客戶(hù)服務(wù)與滿(mǎn)意度提升計(jì)劃
- 2025年噴霧通風(fēng)冷卻塔項(xiàng)目規(guī)劃申請(qǐng)報(bào)告模板
- 2025年餐廚垃圾處理項(xiàng)目可行性分析報(bào)告
- 幼兒園小班社會(huì)行為培養(yǎng)計(jì)劃
- 2025年籃球市場(chǎng)分析報(bào)告
- 2025年小沃科技有限公司-企業(yè)報(bào)告(業(yè)主版)
- 單位駕駛員勞務(wù)派遣投標(biāo)方案投標(biāo)文件(技術(shù)方案)
- 資本經(jīng)營(yíng)-終結(jié)性考試-國(guó)開(kāi)(SC)-參考資料
- 2024年浙江省中考科學(xué)試卷
- 拆除工程地坪拆除施工方案
- 軟件授權(quán)書(shū)范本
- 招聘筆試題與參考答案(某大型國(guó)企)2025年
- DB34∕T 2570-2015 祁紅香螺加工技術(shù)規(guī)程
- 安徽合肥濱湖投資控股集團(tuán)有限公司招聘筆試題庫(kù)2024
- 2024年四年級(jí)英語(yǔ)下冊(cè) Module 4 Things we enjoy Unit 12 The ugly duckling第3課時(shí)教案 牛津滬教版(三起)
- 2024年鐵路職業(yè)技能競(jìng)賽(線(xiàn)路工)理論考試題庫(kù)及答案
- 六年級(jí)畢業(yè)測(cè)試卷(道德與法治)【實(shí)驗(yàn)班】
評(píng)論
0/150
提交評(píng)論