so true

          心懷未來,開創(chuàng)未來!
          隨筆 - 160, 文章 - 0, 評(píng)論 - 40, 引用 - 0
          數(shù)據(jù)加載中……

          進(jìn)程的虛擬地址空間

          昨晚看到了深夜,終于對(duì)進(jìn)程的虛擬地址空間有了個(gè)大致的了解,很激動(dòng),也很欣慰。回頭想來,一個(gè)程序員,真的應(yīng)該知道這些知識(shí),否則還真不太稱職。
          首先告訴大家,我后面提到的這些知識(shí)在《windows核心編程》中都有,強(qiáng)烈建議大家把這本書翻翻,我相信會(huì)對(duì)你的編程境界拔高好幾個(gè)層次的。可是我最近沒那么多時(shí)間,因此就只能了解個(gè)大概,然后等今后閑暇時(shí)再看這本書吧。
          昨天我媳婦還反復(fù)和我說:學(xué)東西必須要有選擇,不能對(duì)IT行業(yè)的所有知識(shí)亂學(xué)習(xí),而且不要學(xué)那種實(shí)際意義不大的知識(shí)或是容易被淘汰的知識(shí)。其實(shí)她說的蠻對(duì)的,但是我要說,有關(guān)《windows核心編程》里的知識(shí)永遠(yuǎn)都不會(huì)過時(shí),因?yàn)樗秩氲降讓雍蛢?nèi)部了,就像C++,你覺得會(huì)過時(shí)嗎?就像windows永遠(yuǎn)不會(huì)被淘汰一樣,呵呵。

          下面我就來粗略的說說我了解的一些基本知識(shí):
          32位機(jī)器,每個(gè)程序有4G的虛擬地址空間。大致分為4塊,從低地址到高地址依次是:NULL區(qū),用戶區(qū),隔離區(qū),核心區(qū)。用戶私有的數(shù)據(jù)都在用戶區(qū)(當(dāng)然這個(gè)區(qū)里又可以細(xì)分,其中也包括一部分可以共享的內(nèi)容),系統(tǒng)內(nèi)核等東西都在核心區(qū)。總體來說,A進(jìn)程的虛擬地址空間中的內(nèi)容和B進(jìn)程相比,只有各自的用戶區(qū)不一致。通常用戶區(qū)中,進(jìn)程又會(huì)將exe文件(由頭數(shù)據(jù)和段數(shù)據(jù)組成)中定義的代碼段、堆棧段、數(shù)據(jù)段等各個(gè)段映射到用戶區(qū)的特定不同部位。對(duì)于這部分區(qū)域,用戶需要用VirtualAlloc先為自己預(yù)留后再提交,最后在自己的頁面被cpu訪問時(shí)再從exe映像中將數(shù)據(jù)加載到主存,然后將虛擬地址映射為主存的物理地址。基本上這樣就可以了,至于系統(tǒng)如何進(jìn)行頁面的管理以及地址映射如何實(shí)現(xiàn)等細(xì)節(jié)請(qǐng)大家再參考別的文獻(xiàn)。

          我本以為很復(fù)雜呢,結(jié)果寫出來,就這么一小段,呵呵,看來是高估了自己理解的東西了,呵呵。

          下面貼出我看的一些資料:

          虛擬存儲(chǔ)器是一個(gè)抽象概念,它為每一個(gè)進(jìn)程提供了一個(gè)假象,好像每個(gè)進(jìn)程都在獨(dú)占的使用主存。每個(gè)進(jìn)程看到的存儲(chǔ)器都是一致的,稱之為虛擬地址空間。

               每個(gè)進(jìn)程看到得虛擬地址空間有大量準(zhǔn)確定義的區(qū)(area)構(gòu)成,每個(gè)區(qū)都有專門的功能。從最低的地址看起

          • 程序代碼和數(shù)據(jù):代碼是從同一固定地址開始,緊接著的是和C全局變量相對(duì)應(yīng)的數(shù)據(jù)區(qū)。 (應(yīng)該就是所謂的靜態(tài)存儲(chǔ)空間)
          • 堆:代碼和數(shù)據(jù)區(qū)后緊隨著的是運(yùn)行時(shí)堆。作為調(diào)用mallocfree這樣的C標(biāo)準(zhǔn)庫函數(shù),堆可以在運(yùn)行時(shí)動(dòng)態(tài)的擴(kuò)展和收縮。(應(yīng)該就是所謂的動(dòng)態(tài)存儲(chǔ)區(qū))
          • 共享庫:在地址空間的中間附近是一塊用來存放像C標(biāo)準(zhǔn)庫和數(shù)學(xué)庫這樣共享庫的代碼和數(shù)據(jù)的區(qū)域。(C標(biāo)準(zhǔn)庫函數(shù)的指令,連接階段把他們加入到編譯后的程序)
          • :位于用戶虛擬地址空間頂部的是用戶,編譯器用它來實(shí)現(xiàn)函數(shù)調(diào)用。和堆一樣每次我們從函數(shù)返回時(shí),就會(huì)收縮。
          • 內(nèi)核虛擬存儲(chǔ)器:內(nèi)核是操作系統(tǒng)總是駐留在存儲(chǔ)器中的部分。地址空間頂部的四分之一部分是為內(nèi)核預(yù)留的。(系統(tǒng)函數(shù)?這里說的UNIX系統(tǒng),不知道windows下是不是這樣的?)

               今天大多數(shù)計(jì)算機(jī)的字長(zhǎng)都是32字節(jié),這就限制了虛擬地址空間為4千兆字節(jié)(4GB

          引言

            Windows的內(nèi)存結(jié)構(gòu)是深入理解Windows操作系統(tǒng)如何運(yùn)作的關(guān)鍵之所在,通過對(duì)內(nèi)存結(jié)構(gòu)的認(rèn)識(shí)可清楚地了解諸如進(jìn)程間數(shù)據(jù)的共享、對(duì)內(nèi)存進(jìn)行有效的管理等問題,從而能夠在程序設(shè)計(jì)時(shí)使程序以更加有效的方式運(yùn)行。Windows操作系統(tǒng)對(duì)內(nèi)存的管理可采取多種不同的方式,其中虛擬內(nèi)存的管理方式可用來管理大型的對(duì)象和結(jié)構(gòu)數(shù)組。

            在Windows系統(tǒng)中,任何一個(gè)進(jìn)程都被賦予其自己的虛擬地址空間,該虛擬地址空間覆蓋了一個(gè)相當(dāng)大的范圍,對(duì)于32位進(jìn)程,其地址空間為232=4,294,967,296 Byte,這使得一個(gè)指針可以使用從0x000000000xFFFFFFFF4GB范圍之內(nèi)的任何一個(gè)值。雖然每一個(gè)32位進(jìn)程可使用4GB的地址空間,但并不意味著每一個(gè)進(jìn)程實(shí)際擁有4GB的物理地址空間,該地址空間僅僅是一個(gè)虛擬地址空間,此虛擬地址空間只是內(nèi)存地址的一個(gè)范圍。進(jìn)程實(shí)際可以得到的物理內(nèi)存要遠(yuǎn)小于其虛擬地址空間。進(jìn)程的虛擬地址空間是為每個(gè)進(jìn)程所私有的,在進(jìn)程內(nèi)運(yùn)行的線程對(duì)內(nèi)存空間的訪問都被限制在調(diào)用進(jìn)程之內(nèi),而不能訪問屬于其他進(jìn)程的內(nèi)存空間。這樣,在不同的進(jìn)程中可以使用相同地址的指針來指向?qū)儆诟髯哉{(diào)用進(jìn)程的內(nèi)容而不會(huì)由此引起混亂。下面分別對(duì)虛擬內(nèi)存的各具體技術(shù)進(jìn)行介紹。
          地址空間中區(qū)域的保留與釋放

          在進(jìn)程創(chuàng)建之初并被賦予地址空間時(shí),其虛擬地址空間尚未分配,處于空閑狀態(tài)。這時(shí)地址空間內(nèi)的內(nèi)存是不能使用的,必須首先通過VirtualAlloc()函數(shù)來分配其內(nèi)的各個(gè)區(qū)域,對(duì)其進(jìn)行保留

          LPVOID VirtualAlloc(
           LPVOID lpAddress,
           DWORD dwSize,
           DWORD flAllocationType,
           DWORD flProtect
          );

          其參數(shù)lpAddress包含一個(gè)內(nèi)存地址,用于定義待分配區(qū)域的首地址。通常可將此參數(shù)設(shè)置為NULL,由系統(tǒng)通過搜索地址空間來決定滿足條件的未保留地址空間。這時(shí)系統(tǒng)可從地址空間的任意位置處開始保留一個(gè)區(qū)域,而且還可以通過向參數(shù)flAllocationType設(shè)置MEM_TOP_DOWN標(biāo)志來指明在盡可能高的地址上分配內(nèi)存。如果不希望由系統(tǒng)自動(dòng)完成對(duì)內(nèi)存區(qū)域的分配而為lpAddress設(shè)定了內(nèi)存地址(必須確保其始終位于進(jìn)程的用戶模式分區(qū)中,否則將會(huì)導(dǎo)致分配的失敗),那么系統(tǒng)將在進(jìn)行分配之前首先檢查在該內(nèi)存地址上是否存在足夠大的未保留空間,如果存在一個(gè)足夠大的空閑區(qū)域,那么系統(tǒng)將會(huì)保留此區(qū)域并返回此保留區(qū)域的虛擬地址,否則將導(dǎo)致分配的失敗而返回NULL。這里需要特別指出的是,在指定lpAddress的內(nèi)存地址時(shí),必須確保是從一個(gè)分配粒度的邊界處開始。
          一般來說,在不同的CPU平臺(tái)下分配粒度各不相同,但目前所有Windows環(huán)境下的CPUx8632Alpha64Alpha以及IA-64等均是采用64KB的分配粒度。如果保留區(qū)域的起始地址沒有遵循從64KB分配粒度的邊界開始之一原則,系統(tǒng)將自動(dòng)調(diào)整該地址到最接近的64K的倍數(shù)。例如,如果指定的lpAddress0x00781022,那么此保留區(qū)域?qū)嶋H是從0x00780000開始分配的。參數(shù)dwSize指定了保留區(qū)域的大小。但是系統(tǒng)實(shí)際保留的區(qū)域大小必須是CPU頁面大小的整數(shù),如果指定的dwSize并非CPU頁面的整數(shù)系統(tǒng)將自動(dòng)對(duì)其進(jìn)行調(diào)整,使其達(dá)到與之最接近的頁面大小整數(shù)與分配粒度一樣,對(duì)于不同的CPU平臺(tái)其頁面大小也是不一樣的。在x86平臺(tái)下,頁面大小為4KB,在32Alpah平臺(tái)下,頁面大小為8KB。在使用時(shí)可以通過GetSystemInfo()來決定當(dāng)前主機(jī)的頁面大小。參數(shù)flAllocationTypeflProtect分別定義了分配類型和訪問保護(hù)屬性。由于VirtualAlloc()可用來保留一個(gè)區(qū)域也可以用來占用物理存儲(chǔ)器,因此通過flAllocationType來指定當(dāng)前是要保留一個(gè)區(qū)域還是要占用物理存儲(chǔ)器。其可能使用的內(nèi)存分配類型有:

          分配類型

          類型說明

          MEM_COMMIT

          為特定的頁面區(qū)域分配內(nèi)存中或磁盤的頁面文件中的物理存儲(chǔ)

          MEM_PHYSICAL

          分配物理內(nèi)存(僅用于地址窗口擴(kuò)展內(nèi)存)

          MEM_RESERVE

          保留進(jìn)程的虛擬地址空間,而不分配任何物理存儲(chǔ)。保留頁面可通過繼續(xù)調(diào)用VirtualAlloc()而被占用

          MEM_RESET

          指明在內(nèi)存中由參數(shù)lpAddressdwSize指定的數(shù)據(jù)無效

          MEM_TOP_DOWN

          在盡可能高的地址上分配內(nèi)存(Windows 98忽略此標(biāo)志)

          MEM_WRITE_WATCH

          必須與MEM_RESERVE一起指定,使系統(tǒng)跟蹤那些被寫入分配區(qū)域的頁面(僅針對(duì)Windows 98


            分配成功完成后,即在進(jìn)程的虛擬地址空間中保留了一個(gè)區(qū)域,可以對(duì)此區(qū)域中的內(nèi)存進(jìn)行保護(hù)權(quán)限許可范圍內(nèi)的訪問。當(dāng)不再需要訪問此地址空間區(qū)域時(shí),應(yīng)釋放此區(qū)域。由VirtualFree()負(fù)責(zé)完成。其函數(shù)原型為:

          BOOL VirtualFree(
           LPVOID lpAddress,
           DWORD dwSize,
           DWORD dwFreeType
          );

          其中,參數(shù)lpAddress為指向待釋放頁面區(qū)域的指針。如果參數(shù)dwFreeType指定了MEM_RELEASE,則lpAddress必須為頁面區(qū)域被保留時(shí)由VirtualAlloc()所返回的基地址。參數(shù)dwSize指定了要釋放的地址空間區(qū)域的大小,如果參數(shù)dwFreeType指定了MEM_RELEASE標(biāo)志,則將dwSize設(shè)置為0,由系統(tǒng)計(jì)算在特定內(nèi)存地址上的待釋放區(qū)域的大小。參數(shù)dwFreeType為所執(zhí)行的釋放操作的類型,其可能的取值為MEM_RELEASEMEM_DECOMMIT,其中MEM_RELEASE標(biāo)志指明要釋放指定的保留頁面區(qū)域,MEM_DECOMMIT標(biāo)志則對(duì)指定的占用頁面區(qū)域進(jìn)行占用的解除。如果VirtualFree()成功執(zhí)行完成,將回收全部范圍的已分配頁面,此后如再對(duì)這些已釋放頁面區(qū)域內(nèi)存的訪問將引發(fā)內(nèi)存訪問異常。釋放后的頁面區(qū)域可供系統(tǒng)繼續(xù)分配使用。

            下面這段代碼演示了由系統(tǒng)在進(jìn)程的用戶模式分區(qū)內(nèi)保留一個(gè)64KB大小的區(qū)域,并將其釋放的過程:

          // 在地址空間中保留一個(gè)區(qū)域

          LPBYTE bBuffer = (LPBYTE)VirtualAlloc(NULL, 65536, MEM_RESERVE, PAGE_READWRITE);

          ……

          // 釋放已保留的區(qū)域

          VirtualFree(bBuffer, 0, MEM_RELEASE);

          flProtect頁面保護(hù)屬性

          我們可以給每個(gè)已分配的物理存儲(chǔ)頁指定不同的頁面保護(hù)屬性。表13-3列出了所有的頁面保護(hù)屬性。

          13-3  內(nèi)存頁面保護(hù)屬性

          保護(hù)屬性

           

          PAGE_NOACCESS

          試圖讀取頁面、寫入頁面或執(zhí)行頁面中的代碼將引發(fā)訪問違規(guī)

          PAGE_READONLY

          試圖寫入頁面或執(zhí)行頁面中的代碼將引發(fā)訪問違規(guī)

          PAGE_READWRITE

          試圖執(zhí)行頁面中的代碼將引發(fā)訪問違規(guī)

          PAGE_EXECUTE

          試圖讀取頁面或?qū)懭腠撁鎸⒁l(fā)訪問違規(guī)

          PAGE_EXECUTE_READ

          試圖寫入頁面將引發(fā)訪問違規(guī)

          PAGE_EXECUTE_READWRITE

          對(duì)頁面執(zhí)行任何操作都不會(huì)引發(fā)訪問違規(guī)

          PAGE_WRITECOPY

          試圖執(zhí)行頁面中的代碼將引發(fā)訪問違規(guī)。試圖寫入頁面將使系統(tǒng)為進(jìn)程單獨(dú)創(chuàng)建一份該頁面的私有副本(以頁交換文件為后備存儲(chǔ)器)

          PAGE_EXECUTE_WRITECOPY

          對(duì)頁面執(zhí)行任何操作都不會(huì)引發(fā)訪問違規(guī)。試圖寫入頁面將使系統(tǒng)為進(jìn)程單獨(dú)創(chuàng)建一份該頁面的私有副本(以頁交換文件為后備存儲(chǔ)器)

          一些惡意軟件將代碼寫入到用于數(shù)據(jù)的內(nèi)存區(qū)域(比如線程),通過這種方式讓應(yīng)用程序執(zhí)行惡意代碼。Windows數(shù)據(jù)執(zhí)行保護(hù)(Data Execution Protection,后面簡(jiǎn)稱為DEP)特性提供了對(duì)此類惡意攻擊的防護(hù)。如果啟用了DEP,那么只有對(duì)那些真正需要執(zhí)行代碼的內(nèi)存區(qū)域,操作系統(tǒng)才會(huì)使用PAGE_EXECUTE_*保護(hù)屬性。其他保護(hù)屬性(最常見的就是PAGE_READWRITE)用于只應(yīng)該存放數(shù)據(jù)的內(nèi)存區(qū)域(比如線程和應(yīng)用程序的堆)

          如果CPU試圖執(zhí)行某個(gè)頁面中的代碼,而該頁又沒有PAGE_EXECUTE_*保護(hù)屬性,那么CPU會(huì)拋出訪問違規(guī)異常。

          系統(tǒng)還對(duì)Windows支持的結(jié)構(gòu)化異常處理機(jī)制(structured exception handling mechanism)做了更進(jìn)一步的保護(hù),結(jié)構(gòu)化異常處理機(jī)制會(huì)在第2325章詳細(xì)介紹。如果應(yīng)用程序在鏈接時(shí)使用了/SAFESEH開關(guān),那么異常處理器會(huì)被注冊(cè)到映像文件中一個(gè)特殊的表中。這樣,當(dāng)將要執(zhí)行一個(gè)異常處理器時(shí),操作系統(tǒng)會(huì)先檢查該處理器有沒有在表中注冊(cè)過,然后決定是否允許它執(zhí)行。

          有關(guān)DEP的更多信息,請(qǐng)?jiān)L問http://go.microsoft.com/fwlink/?LinkId=28022,可以在此找到Microsoft白皮書“03_CIF_Memory_Protection.DOC”。

                                                                                 

          13.6.1  寫時(shí)復(fù)制

          在表13.3中列出的保護(hù)屬性中,除最后兩個(gè)屬性PAGE_WRITECOPYPAGE_EXECUTE_WRITECOPY之外,其余的都不言自明。這兩個(gè)保護(hù)屬性存在的目的是為了節(jié)省內(nèi)存和頁交換文件的使用。Windows支持一種機(jī)制,允許兩個(gè)或兩個(gè)以上的進(jìn)程共享同一塊存儲(chǔ)器。因此,如果有10個(gè)記事本程序正在運(yùn)行,所有的進(jìn)程會(huì)共享應(yīng)用程序的代碼頁和數(shù)據(jù)頁。讓所有的應(yīng)用程序?qū)嵗蚕硐嗤?span id="wmqeeuq" class="GramE">存儲(chǔ)頁極大地提升了系統(tǒng)的性能,但另一方面,這也要求所有的應(yīng)用程序?qū)嵗荒茏x取其中的數(shù)據(jù)或是執(zhí)行其中的代碼。如果某個(gè)應(yīng)用程序?qū)嵗薷牟懭胍粋€(gè)存儲(chǔ)頁,那么這等于是修改了其他實(shí)例正在使用的存儲(chǔ)頁,最終將導(dǎo)致混亂。

          為了避免此類混亂的發(fā)生,操作系統(tǒng)會(huì)給共享的存儲(chǔ)頁指定寫時(shí)復(fù)制屬性。當(dāng)系統(tǒng)把一個(gè).exe.dll映射到一個(gè)地址空間的時(shí)候,系統(tǒng)會(huì)計(jì)算有多少頁面是可寫的。(通常,包含代碼的頁面被標(biāo)記為PAGE_EXECUTE_READ,而包含數(shù)據(jù)的頁面被標(biāo)記為PAGE_READWRITE)然后系統(tǒng)會(huì)從頁交換文件中分配存儲(chǔ)空間來容納這些可寫頁面。除非應(yīng)用程序真的寫入可寫頁面,否則不會(huì)用到頁交換文件中的存儲(chǔ)器。

          當(dāng)線程試圖寫入一個(gè)共享頁面時(shí),系統(tǒng)會(huì)介入并執(zhí)行下面的操作。

          (1)   系統(tǒng)在內(nèi)存中找到一個(gè)閑置頁面。注意,該閑置頁面的后備頁面來自頁交換文件,它是系統(tǒng)最初將模塊映射到進(jìn)程的地址空間時(shí)分配的。由于系統(tǒng)在第一次進(jìn)行映射的時(shí)候分配了所有可能需要的頁交換文件空間,這一步不可能失敗。

          (2)   系統(tǒng)把線程想要修改的頁面內(nèi)容復(fù)制到在第1步中找到的閑置頁面。系統(tǒng)會(huì)給該閑置頁面指定PAGE_READWRITEPAGE_EXECUTE_READWRITE保護(hù)屬性,系統(tǒng)不會(huì)對(duì)原始頁面的保護(hù)屬性和數(shù)據(jù)做任何修改。

          (3)   然后,系統(tǒng)更新進(jìn)程的頁面表,這樣一來,原來的虛擬地址現(xiàn)在就對(duì)應(yīng)到內(nèi)存中一個(gè)新的頁面了。

          系統(tǒng)在執(zhí)行這些步驟之后,進(jìn)程就可以訪問它自己的副本了。第17章將進(jìn)一步介紹存儲(chǔ)器共享和寫時(shí)復(fù)制。

          此外,在預(yù)訂地址空間或調(diào)撥物理存儲(chǔ)器時(shí),不能使用PAGE_WRITECOPYPAGE_EXECUTE_WRITECOPY保護(hù)屬性。這樣做會(huì)導(dǎo)致調(diào)用VirtualAlloc失敗,此時(shí)調(diào)用GetLastError會(huì)返回錯(cuò)誤碼ERROR_INVALID_PARAMETER。這兩個(gè)屬性是操作系統(tǒng)在映射.exeDLL映像文件時(shí)用的。

          13.6.2  一些特殊的訪問保護(hù)屬性標(biāo)志

          除了已經(jīng)介紹過的保護(hù)屬性之外,另外還有3個(gè)保護(hù)屬性標(biāo)志:PAGE_NOCACHEPAGE_WRITECOMBINEPAGE_GUARD。使用這些標(biāo)志時(shí),只需將它們與除了PAGE_NOACCESS之外的任何其他保護(hù)屬性進(jìn)行按位或操作即可。

          第一個(gè)保護(hù)屬性標(biāo)志PAGE_NOCACHE,用來禁止對(duì)已調(diào)撥的頁面進(jìn)行緩存。該標(biāo)志存在的主要目的是為了讓需要操控內(nèi)存緩沖區(qū)的驅(qū)動(dòng)程序開發(fā)人員使用,不建議將該標(biāo)志用于除此以外的其他用途。

                                                                                 

          第二個(gè)保護(hù)屬性標(biāo)志PAGE_WRITECOMBINE也是給驅(qū)動(dòng)程序開發(fā)人員用的。它允許把對(duì)單個(gè)設(shè)備的多次寫操作組合在一起,以提高性能。

          最后一個(gè)保護(hù)屬性標(biāo)志PAGE_GUARD,使應(yīng)用程序能夠在頁面中的任何一個(gè)字節(jié)被寫入時(shí)得到通知。這個(gè)標(biāo)志有一些巧妙的用法。Windows在創(chuàng)建線程時(shí)會(huì)用到它。有關(guān)該標(biāo)志的更多信息,請(qǐng)參閱第16章。


          物理存儲(chǔ)器的提交與回收

            在地址空間中保留一個(gè)區(qū)域后,并不能直接對(duì)其進(jìn)行使用,必須在把物理存儲(chǔ)器提交給該區(qū)域后,才可以訪問區(qū)域中的內(nèi)存地址。在提交過程中,物理存儲(chǔ)器是按頁面邊界和頁面大小的塊來進(jìn)行提交的。若要為一個(gè)已保留的地址空間區(qū)域提交物理存儲(chǔ)器,需要再次調(diào)用VirtualAlloc()函數(shù),所不同的是在執(zhí)行物理存儲(chǔ)器的提交過程中需要指定flAllocationType參數(shù)為MEM_COMMIT標(biāo)志,使用的保護(hù)屬性與保留區(qū)域時(shí)所用保護(hù)屬性一致。在提交時(shí),可以將物理存儲(chǔ)器提交給整個(gè)保留區(qū)域,也可以進(jìn)行部分提交,由VirtualAlloc()函數(shù)的lpAddress參數(shù)和dwSize參數(shù)指明要將物理存儲(chǔ)器提交到何處以及要提交多少物理存儲(chǔ)器。
          與保留區(qū)域的釋放類似,當(dāng)不再需要訪問保留區(qū)域中被提交的物理存儲(chǔ)器時(shí),提交的物理存儲(chǔ)器應(yīng)得到及時(shí)的釋放。該回收過程與保留區(qū)域的釋放一樣也是通過VirtualFree()函數(shù)來完成的。在調(diào)用時(shí)為VirtualFree()的dwFreeType參數(shù)指定MEM_DECOMMIT標(biāo)志,并在參數(shù)lpAddressdwSize中傳遞用來標(biāo)識(shí)要解除的第一個(gè)頁面的內(nèi)存地址和釋放的字節(jié)數(shù)。此回收過程同樣也是以頁面為單位來進(jìn)行的,將回收設(shè)定范圍所涉及到的所有頁面。下面這段代碼演示了對(duì)先前保留區(qū)域的提交過程,并在使用完畢后將其回收:

          //
          在地址空間中保留一個(gè)區(qū)域

          LPBYTE bBuffer = (LPBYTE)VirtualAlloc(NULL, 65536, MEM_RESERVE, PAGE_READWRITE);

          // 提交物理存儲(chǔ)器

          VirtualAlloc(bBuffer, 65536, MEM_COMMIT, PAGE_READWRITE);

          ……

          // 回收提交的物理存儲(chǔ)器

          VirtualFree(bBuffer, 65536, MEM_DECOMMIT);

          // 釋放已保留的區(qū)域

          VirtualFree(bBuffer, 0, MEM_RELEASE);

           

            由于未經(jīng)提交的保留區(qū)域?qū)嶋H是無法使用的,因此在編程過程中允許通過一次VirtualAlloc()調(diào)用而完成對(duì)地址空間的區(qū)域保留及對(duì)保留區(qū)域的物理存儲(chǔ)器的提交。相應(yīng)的,回收、釋放過程也可由一次VirtualFree()調(diào)用來實(shí)現(xiàn)。上述代碼可按此方法改寫為:

          // 在地址空間中保留一個(gè)區(qū)域并提交物理存儲(chǔ)器

          LPBYTE bBuffer = (LPBYTE)VirtualAlloc(NULL, 65536, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

          ……

          // 釋放已保留的區(qū)域并回收提交的物理存儲(chǔ)器

          VirtualFree(bBuffer, 0, MEM_RELEASE | MEM_DECOMMIT); 

          頁文件的使用

            在前面曾多次提到物理存儲(chǔ)器,這里所說的物理存儲(chǔ)器并不局限于計(jì)算機(jī)內(nèi)存,還包括在磁盤空間上創(chuàng)建的頁文件,其存儲(chǔ)空間大小為計(jì)算機(jī)內(nèi)存和頁文件存儲(chǔ)容量之。由于通常情況下磁盤存儲(chǔ)空間要遠(yuǎn)大于內(nèi)存的存儲(chǔ)空間,因此頁文件的使用對(duì)于應(yīng)用程序而言相當(dāng)于透明的增加了其所能使用的內(nèi)存容量。在使用時(shí),由操作系統(tǒng)和CPU負(fù)責(zé)對(duì)頁文件進(jìn)行維護(hù)和協(xié)調(diào)。只有在應(yīng)用程序需要時(shí)才臨時(shí)將頁文件中的數(shù)據(jù)加載到內(nèi)存供應(yīng)用程序訪問之用,在使用完畢后再從內(nèi)存交換回頁文件

          進(jìn)程中的線程在訪問位于已提交物理存儲(chǔ)器的保留區(qū)域的內(nèi)存地址時(shí),如果此地址指向的數(shù)據(jù)當(dāng)前已存在于內(nèi)存,CPU將直接將進(jìn)程的虛擬地址映射為物理地址,并完成對(duì)數(shù)據(jù)的訪問;如果此數(shù)據(jù)是存在于頁文件中的,就要試圖將此數(shù)據(jù)從頁文件加載到內(nèi)存。在進(jìn)行此處理時(shí),首先要檢查內(nèi)存中是否有可供使用的空閑頁面,如果有就可以直接將數(shù)據(jù)加載到內(nèi)存中的空閑頁面,否則就要從內(nèi)存中尋找一個(gè)暫不使用的可釋放的頁面并將數(shù)據(jù)加載到此頁面。如果被釋放頁面中的數(shù)據(jù)仍為有效數(shù)據(jù)(即以后還會(huì)用到),就要先將此頁面從內(nèi)存寫入到頁文件。在數(shù)據(jù)加載到內(nèi)存后,仍要在CPU將虛擬地址映射為物理地址后方可實(shí)現(xiàn)對(duì)數(shù)據(jù)的訪問。與對(duì)物理存儲(chǔ)器中數(shù)據(jù)的訪問有所不同,在運(yùn)行可執(zhí)行程序時(shí)并不進(jìn)行程序代碼和數(shù)據(jù)的從磁盤文件到頁文件的復(fù)制過程,而是在確定了程序的代碼及其數(shù)據(jù)的大小后,由系統(tǒng)直接將可執(zhí)行程序的映像用作程序的保留地址空間區(qū)域。這樣的處理方式大大縮短了程序的啟動(dòng)時(shí)間,并可減小頁文件的尺寸。

           

           

          上面提到的“數(shù)據(jù)是否在內(nèi)存中”,我認(rèn)為應(yīng)該是判斷系統(tǒng)緩存中是否有需要的頁面。

          ==========================================================================================

          對(duì)內(nèi)存的管理

            使用虛擬內(nèi)存技術(shù)將能夠?qū)?nèi)存進(jìn)行管理。對(duì)當(dāng)前內(nèi)存狀態(tài)的動(dòng)態(tài)信息可通過GlobalMemoryStatus()函數(shù)來獲取。GlobalMemoryStatus()的函數(shù)原型為:

           

          VOID GlobalMemoryStatus(LPMEMORYSTATUS lpBuffer);

           

            其參數(shù)lpBuffer為一個(gè)指向內(nèi)存狀態(tài)結(jié)構(gòu)MEMORYSTATUS的指針,而且要預(yù)先對(duì)該結(jié)構(gòu)對(duì)象的數(shù)據(jù)成員進(jìn)行初始化。MEMORYSTATUS結(jié)構(gòu)定義如下:

           

          typedef struct _MEMORYSTATUS {

           DWORD dwLength; // MEMORYSTATUS結(jié)構(gòu)大小

           DWORD dwMemoryLoad; // 已使用內(nèi)存所占的百分比

           DWORD dwTotalPhys; // 物理存儲(chǔ)器的總字節(jié)數(shù)

           DWORD dwAvailPhys; // 空閑物理存儲(chǔ)器的字節(jié)數(shù)

           DWORD dwTotalPageFile; // 頁文件包含的最大字節(jié)數(shù)

           DWORD dwAvailPageFile; // 頁文件可用字節(jié)數(shù)

           DWORD dwTotalVirtual; // 用戶模式分區(qū)大小

           DWORD dwAvailVirtual; // 用戶模式分區(qū)中空閑內(nèi)存大小

          } MEMORYSTATUS, *LPMEMORYSTATUS;

          下面這段代碼通過設(shè)置一個(gè)定時(shí)器而每隔5秒更新一次當(dāng)前系統(tǒng)對(duì)內(nèi)存的使用情況:

          // 設(shè)置定時(shí)器

          SetTimer(0, 5000, NULL);

          ……

          void CSample22Dlg::OnTimer(UINT nIDEvent)

          {

           // 獲取當(dāng)前內(nèi)存使用狀態(tài)

           MEMORYSTATUS mst;

           GlobalMemoryStatus(&mst);

           // 已使用內(nèi)存所占的百分比

           m_dwMemoryLoad = mst.dwMemoryLoad;

           // 物理存儲(chǔ)器的總字節(jié)數(shù)

           m_dwAvailPhys = mst.dwAvailPhys / 1024;

           // 空閑物理存儲(chǔ)器的字節(jié)數(shù)

           m_dwAvailPageFile = mst.dwAvailPageFile / 1024;

           // 頁文件包含的最大字節(jié)數(shù)

           m_dwAvailVirtual = mst.dwAvailVirtual / 1024;

           // 頁文件可用字節(jié)數(shù)

           m_dwTotalPageFile = mst.dwTotalPageFile / 1024;

           // 用戶模式分區(qū)大小

           m_dwTotalPhys = mst.dwTotalPhys / 1024;

           // 用戶模式分區(qū)中空閑內(nèi)存大小

           m_dwTotalVirtual = mst.dwTotalVirtual / 1024;

           // 更新顯示

           UpdateData(FALSE);

           CDialog::OnTimer(nIDEvent);

          }

           

            對(duì)內(nèi)存的管理除了對(duì)當(dāng)前內(nèi)存的使用狀態(tài)信息進(jìn)行獲取外,還經(jīng)常需要獲取有關(guān)進(jìn)程的虛擬地址空間的狀態(tài)信息。可由VirtualQuery()函數(shù)來進(jìn)行查詢,其原型聲明如下:

           

          DWORD VirtualQuery(

           LPCVOID lpAddress, // 內(nèi)存地址

           PMEMORY_BASIC_INFORMATION lpBuffer, // 指向內(nèi)存信息結(jié)構(gòu)的指針

           DWORD dwLength // 內(nèi)存的大小

          );

           

            其中lpAddress參數(shù)為要查詢的虛擬內(nèi)存地址,該值將被調(diào)整到最近的頁邊界處。當(dāng)前計(jì)算機(jī)的頁面大小可通過GetSystemInfo()函數(shù)獲取,該函數(shù)需要一個(gè)指向SYSTEM_INFO結(jié)構(gòu)的指針作為參數(shù),獲取到的系統(tǒng)信息將填充在該數(shù)據(jù)結(jié)構(gòu)對(duì)象中。下面這段代碼通過對(duì)GetSystemInfo()的調(diào)用而獲取了當(dāng)前的系統(tǒng)信息:

           

          // 得到當(dāng)前系統(tǒng)信息

          GetSystemInfo(&m_sin);

          // 位屏蔽,指明哪個(gè)CPU是活動(dòng)的

          m_dwActiveProcessorMask = m_sin.dwActiveProcessorMask;

          // 保留的地址空間區(qū)域的分配粒度

          m_dwAllocationGranularity = m_sin.dwAllocationGranularity;

          // 進(jìn)程的可用地址空間的最小內(nèi)存地址

          m_dwMaxApplicationAddress = (DWORD)m_sin.lpMaximumApplicationAddress;

          // 進(jìn)程的可用地址空間的最大內(nèi)存地址

          m_dwMinApplicationAddress = (DWORD)m_sin.lpMinimumApplicationAddress;

          // 計(jì)算機(jī)中CPU的數(shù)目

          m_dwNumberOfProcessors = m_sin.dwNumberOfProcessors;

          // 頁面大小

          m_dwPageSize = m_sin.dwPageSize;

          // 處理器類型

          m_dwProcessorType = m_sin.dwProcessorType;

          //進(jìn)一步細(xì)分處理器級(jí)別

          m_wProcessorLevel = m_sin.wProcessorLevel;

          // 系統(tǒng)處理器的結(jié)構(gòu)

          m_wProcessorArchitecture = m_sin.wProcessorArchitecture;

          // 更新顯示

          UpdateData(FALSE);

          VirtualQuery()的第二個(gè)參數(shù)lpBuffer為一個(gè)指向MEMORY_BASIC_INFORMATION結(jié)構(gòu)的指針。VirtualQuery()如成功執(zhí)行,該結(jié)構(gòu)對(duì)象中將保存查詢到的虛擬地址空間狀態(tài)信息。MEMORY_BASIC_INFORMATION結(jié)構(gòu)的定義為:

          typedef struct _MEMORY_BASIC_INFORMATION {

           PVOID BaseAddress; // 保留區(qū)域的基地址

           PVOID AllocationBase; // 分配的基地址

           DWORD AllocationProtect; // 初次保留時(shí)所設(shè)置的保護(hù)屬性

           DWORD RegionSize; // 區(qū)域大小

           DWORD State; // 狀態(tài)(提交、保留或空閑)

           DWORD Protect; // 當(dāng)前訪問保護(hù)屬性

           DWORD Type; // 頁面類型

          } MEMORY_BASIC_INFORMATION; 

           

            通過VirtualQuery()函數(shù)對(duì)由lpAddressdwLength參數(shù)指定的虛擬地址空間區(qū)域的查詢而獲取得到的相關(guān)狀態(tài)信息:

           

          // 更新顯示

          UpdateData(TRUE);

          // 虛擬地址空間狀態(tài)結(jié)構(gòu)

          MEMORY_BASIC_INFORMATION mbi;

          // 查詢指定虛擬地址空間的狀態(tài)信息

          VirtualQuery((LPCVOID)m_dwAddress, &mbi, 1024);

          // 保留區(qū)域的基地址

          m_dwBaseAddress = (DWORD)mbi.BaseAddress;

          // 分配的基地址

          m_dwAllocateBase = (DWORD)mbi.AllocationBase;

          // 初次保留時(shí)所設(shè)置的保護(hù)屬性

          m_dwAllocateProtect = mbi.AllocationProtect;

          // 區(qū)域大小

          m_dwRegionSize = mbi.RegionSize;

          // 狀態(tài)(提交、保留或空閑)

          m_dwState = mbi.State;

          // 當(dāng)前訪問保護(hù)屬性

          m_dwProtect = mbi.Protect;

          // 頁面類型

          m_dwType = mbi.Type;

          // 更新顯示

          UpdateData(FALSE);

           

            小結(jié)

           

            本文主要對(duì)內(nèi)存管理中的虛擬內(nèi)存技術(shù)的基本原理、使用方法和對(duì)內(nèi)存的管理等進(jìn)行了介紹。通過本文將能夠掌握虛擬內(nèi)存的一般使用方法,與之相關(guān)的內(nèi)存管理技術(shù)還包括內(nèi)存文件映射和堆管理等技術(shù),讀者可參閱相關(guān)文章。這幾種內(nèi)存管理技術(shù)同屬Windows編程中的高級(jí)技術(shù),在應(yīng)用程序中適當(dāng)使用將有助于程序性能的提高。本文所述程序在Windows 2000 Professional下由Microsoft Viusual C++ 6.0編譯通過。

          進(jìn)程的虛擬地址空間

          每個(gè)進(jìn)程都被賦予它自己的虛擬地址空間。對(duì)于3 2位進(jìn)程來說,這個(gè)地址空間是4 G B,因?yàn)?span lang="EN-US">3 2位指針可以擁有從0 x 0 0 0 0 0 0 0 00 x F F F F F F F F之間的任何一個(gè)值。這使得一個(gè)指針能夠擁有4 294 967 296個(gè)值中的一個(gè)值,它覆蓋了一個(gè)進(jìn)程的4 G B虛擬空間的范圍。對(duì)于6 4位進(jìn)程來說,這個(gè)地址空間是1 6 E B1 01 8字節(jié)),因?yàn)?span lang="EN-US">6 4位指針可以擁有從0 x 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 x F F F F F F F F F F F F F F F F之間的任何值。這使得一個(gè)指針可以擁有18 446 744 073 709 551 616個(gè)值中的一個(gè)值,它覆蓋了一個(gè)進(jìn)程的1 6 E B虛擬空間的范圍。這是相當(dāng)大的一個(gè)范圍。

          由于每個(gè)進(jìn)程可以接收它自己的私有的地址空間,因此當(dāng)進(jìn)程中的一個(gè)線程正在運(yùn)行時(shí),該線程可以訪問只屬于它的進(jìn)程的內(nèi)存。屬于所有其他進(jìn)程的內(nèi)存則隱藏著,并且不能被正在運(yùn)行的線程訪問。

          注意在Windows 2000中,屬于操作系統(tǒng)本身的內(nèi)存也是隱藏的,正在運(yùn)行的線程無法訪問。這意味著線程常常不能訪問操作系統(tǒng)的數(shù)據(jù)。Windows 98中,屬于操作系統(tǒng)的內(nèi)存是不隱藏的,正在運(yùn)行的線程可以訪問。因此,正在運(yùn)行的線程常常可以訪問操作系統(tǒng)的數(shù)據(jù),也可以破壞操作系統(tǒng)(從而有可能導(dǎo)致操作系統(tǒng)崩潰)。在Windows 98中,一個(gè)進(jìn)程的線程不可能訪問屬于另一個(gè)進(jìn)程的內(nèi)存。

          前面說過,每個(gè)進(jìn)程有它自己的私有地址空間。進(jìn)程A可能有一個(gè)存放在它的地址空間中的數(shù)據(jù)結(jié)構(gòu),地址是0 x 1 2 3 4 5 6 7 8而進(jìn)程B則有一個(gè)完全不同的數(shù)據(jù)結(jié)構(gòu)存放在它的地址空間中,地址是0 x 1 2 3 4 5 6 7 8。當(dāng)進(jìn)程A中運(yùn)行的線程訪問地址為0 x 1 2 3 4 5 6 7 8的內(nèi)存時(shí),這些線程訪問的是進(jìn)程A的數(shù)據(jù)結(jié)構(gòu)。當(dāng)進(jìn)程B中運(yùn)行的線程訪問地址為0 x 1 2 3 4 5 6 7 8的內(nèi)存時(shí),這些線程訪問的是進(jìn)程B的數(shù)據(jù)結(jié)構(gòu)。進(jìn)程A中運(yùn)行的線程不能訪問進(jìn)程B的地址空間中的數(shù)據(jù)結(jié)構(gòu)。反之亦然。

          當(dāng)你因?yàn)閾碛腥绱舜蟮牡刂房臻g可以用于應(yīng)用程序而興高采烈之前,記住,這是個(gè)虛擬地址空間,不是物理地址空間。該地址空間只是內(nèi)存地址的一個(gè)范圍。在你能夠成功地訪問數(shù)據(jù)而不會(huì)出現(xiàn)違規(guī)訪問之前,必須賦予物理存儲(chǔ)器或者將物理存儲(chǔ)器映射到各個(gè)部分的地址空間。本章后面將要具體介紹這是如何操作的。

          虛擬地址空間如何分區(qū)

          每個(gè)進(jìn)程的虛擬地址空間都要?jiǎng)澐殖筛鱾€(gè)分區(qū)。地址空間的分區(qū)是根據(jù)操作系統(tǒng)的基本實(shí)現(xiàn)方法來進(jìn)行的。不同的Wi n d o w s內(nèi)核,其分區(qū)也略有不同。表 1顯示了每種平臺(tái)是如何對(duì)進(jìn)程的地址空間進(jìn)行分區(qū)的。

          1 進(jìn)程的地址空間如何分區(qū)

          分區(qū)

          32Windows 2000(x86Alpha處理器)

          32Windows 2000(x86w/3GB用戶方式)

          64Windows 2000(AlphaIA-64處理器)

          Windows 98

          N U L L指針分配的分區(qū)

          0 x 0 0 0 0 0 0 0 0  ——0x 0 0 0 0 F F F F

          0 x 0 0 0 0 0 0 0 0 0 x 0 0 0 0 F F F F

          0x00000000 00000000 0x00000000 0000FFFF

          0 x 0 0 0 0 0 0 0 0 0 x 0 0 0 0 0 F F F

          DOS/16Windows應(yīng)用程序兼容分區(qū)

          0 x 0 0 0 0 0 1 0 0 0 0 x 0 0 3 F F F F F

          用戶方式

          0 x 0 0 0 1 0 0 0 0—— 0 x 7 F F E F F F F<將近2G>

          0 x 0 0 0 1 0 0 0 0 0 x B F F E F F F F F

          0x00000000 00010000 0x000003FF FFFEFFFF

          0 x 0 0 4 0 0 0 0 0 0 x 7 F F F F F F F

          64-KB禁止進(jìn)入分區(qū)

          0 x 7 F F F 0 0 0 0——0x7FFF FFFF

          0 x B F F F 0 0 0 0——0 x B F F F F F F F

          0 x 0 0 0 0 0 3 F F F F F F 0 0 0 0——0 x 0 0 0 0 0 3 F F F F F F F F F F

          共享內(nèi)存映射

          0 x 8 0 0 0 0 0 0 0

          文件(MMF)內(nèi)核方式

          0 x 8 0 0 0 0 0 0 0 —— 0 x F F F F F F F F<2G>

          0 x C 0 0 0 0 0 0 0 0 x F F F F F F F F

          0x00000400 00000000 0xFFFFFFFFF FFFFFFF

          0 x B F F F F F F F 0 x C 0 0 0 0 0 0 0 0 x F F F F F F F F

          1. NULL指針分區(qū)是NULL指針的地址范圍。
              
          對(duì)這個(gè)區(qū)域的讀寫企圖都將引發(fā)訪問違規(guī)。 
          2. DOS/WIN16
          分區(qū)是98中專門用于16位的
              DOS
          windows程序運(yùn)行的空間,所有的16
              
          位程序?qū)⒐蚕磉@個(gè)4M的空間。Win2000中不
              
          存在這個(gè)分區(qū),16位程序也會(huì)擁有自己獨(dú)立的虛擬地址空間。有的文章中稱win2000中不能運(yùn)行16位程序,是不確切的。 
          3.
          用戶分區(qū)是進(jìn)程的私有領(lǐng)域,Win2000中,程序的可執(zhí)行代碼和其它用戶模塊均加載在這里,內(nèi)存映射文件也會(huì)加載在這里。Win98中的系統(tǒng)共享DLL和內(nèi)存映射文件則加載在共享分區(qū)中。 
          4.
          禁止訪問分區(qū)只有在win2000中有。這個(gè)分區(qū)是用戶分區(qū)和內(nèi)核分區(qū)之間的一個(gè)隔離帶,目的是為了防止用戶程序違規(guī)訪問內(nèi)核分區(qū)。 
          5. MMF
          分區(qū)只有win98中有,所有的內(nèi)存映射文件和系統(tǒng)共享DLL將加載在這個(gè)地址。而2000中則將其加載到用戶分區(qū)。 
          6. 
          內(nèi)核方式分區(qū)對(duì)用戶的程序來說是禁止訪問的,操作系統(tǒng)的代碼在此。內(nèi)核對(duì)象也駐留在此。
          另外要說明的是,win98中對(duì)于內(nèi)核分區(qū)本也應(yīng)該提供保護(hù)的,但遺憾的是并沒有做到,因而98中程序可以訪問內(nèi)核分區(qū)的地址空間。
          對(duì)于用戶分區(qū),又可以細(xì)分成若干區(qū)域。(這些區(qū)域具體會(huì)在第四階段詳細(xì)剖析。因?yàn)檫@部分內(nèi)容牽扯到PE文件結(jié)構(gòu),只有學(xué)習(xí)并理解了PE文件結(jié)構(gòu)后,才能理解這部分內(nèi)容,為了便于后面的講解,在此講這部分區(qū)域先大致分為4塊:)

          3 2Windows 2000的內(nèi)核與6 4Windows 2000的內(nèi)核擁有大體相同的分區(qū),差別在于分區(qū)的大小和位置有所不同。另一方面,可以看到Windows 98下的分區(qū)有著很大的不同。下面讓我們看一下系統(tǒng)是如何使用每一個(gè)分區(qū)的。

          NULL指針分配的分區(qū)適用于Windows 2000Windows 98

          進(jìn)程地址空間的這個(gè)分區(qū)的設(shè)置是為了幫助程序員掌握N U L L指針的分配情況。如果你的進(jìn)程中的線程試圖讀取該分區(qū)的地址空間的數(shù)據(jù),或者將數(shù)據(jù)寫入該分區(qū)的地址空間,那么C P U就會(huì)引發(fā)一個(gè)訪問違規(guī)。保護(hù)這個(gè)分區(qū)是極其有用的,它可以幫助你發(fā)現(xiàn)N U L L指針的分配情況。

          C / C + +程序中常常不進(jìn)行嚴(yán)格的錯(cuò)誤檢查。例如,下面這個(gè)代碼就沒有進(jìn)行任何錯(cuò)誤檢查:

          int* pnSomeInteger = (int*) malloc(sizeof(int));
          *pnSomeInteger = 5;

          如果m a l l o c不能找到足夠的內(nèi)存來滿足需要,它就返回N U L L。但是,該代碼并不檢查這種可能性,它認(rèn)為地址的分配已經(jīng)取得成功,并且開始訪問0 x 0 0 0 0 0 0 0 0地址的內(nèi)存。由于這個(gè)分區(qū)的地址空間是禁止進(jìn)入的,因此就會(huì)發(fā)生內(nèi)存訪問違規(guī)現(xiàn)象,同時(shí)該進(jìn)程將終止運(yùn)行。這個(gè)特性有助于編程員發(fā)現(xiàn)應(yīng)用程序中的錯(cuò)誤。

          用戶方式分區(qū)適用于Windows 2000Windows 98

          這個(gè)分區(qū)是進(jìn)程的私有(非共享)地址空間所在的地方。一個(gè)進(jìn)程不能讀取、寫入、或者以任何方式訪問駐留在該分區(qū)中的另一個(gè)進(jìn)程的數(shù)據(jù)。對(duì)于所有應(yīng)用程序來說,該分區(qū)是維護(hù)進(jìn)程的大部分?jǐn)?shù)據(jù)的地方。由于每個(gè)進(jìn)程可以得到它自己的私有的、非共享分區(qū),以便存放它的數(shù)據(jù),因此,應(yīng)用程序不太可能被其他應(yīng)用程序所破壞,這使得整個(gè)系統(tǒng)更加健壯。

          Windows 2000中,所有的. e x eD L L模塊均加載這個(gè)分區(qū)。每個(gè)進(jìn)程可以將這些D L L加載到該分區(qū)的不同地址中(不過這種可能性很小)。系統(tǒng)還可以在這個(gè)分區(qū)中映射該進(jìn)程可以訪問的所有內(nèi)存映射文件

          共享的MMF分區(qū)僅適用于Windows 98

          這個(gè)1 G B分區(qū)是系統(tǒng)用來存放所有3 2位進(jìn)程共享數(shù)據(jù)的地方。例如,系統(tǒng)的動(dòng)態(tài)鏈接庫K e r n e l 3 2 . d l lA d v A P I 3 2 . d l lU s e r 3 2 . d l lG D I 3 2 . d l l等,全部存放在這個(gè)地址空間分區(qū)中,因此,所有3 2位進(jìn)程都能很容易同時(shí)訪問它們。系統(tǒng)還為每個(gè)進(jìn)程將D L L加載相同的內(nèi)存地址。此外,系統(tǒng)將所有內(nèi)存映射文件映射到這個(gè)分區(qū)中。

          物理存儲(chǔ)器與頁文件

          在較老的操作系統(tǒng)中,物理存儲(chǔ)器被視為計(jì)算機(jī)擁有的R A M的容量。換句話說,如果計(jì)算機(jī)擁有1 6 M BR A M,那么加載和運(yùn)行的應(yīng)用程序最多可以使用1 6 M BR A M。今天的操作系統(tǒng)能夠使得磁盤空間看上去就像內(nèi)存一樣。磁盤上的文件通常稱為頁文件,它包含了可供所有進(jìn)程使用的虛擬內(nèi)存

          當(dāng)然,若要使虛擬內(nèi)存能夠運(yùn)行,需要得到C P U本身的大量幫助。當(dāng)一個(gè)線程試圖訪問一個(gè)字節(jié)的內(nèi)存時(shí), C P U必須知道這個(gè)字節(jié)是在R A M中還是在磁盤上。

          從應(yīng)用程序的角度來看,頁文件透明地增加了應(yīng)用程序能夠使用的R A M(即內(nèi)存)的數(shù)量。如果計(jì)算機(jī)擁有6 4 M BR A M,同時(shí)在硬盤上有一個(gè)100 MB的頁文件,那么運(yùn)行的應(yīng)用程序就認(rèn)為計(jì)算機(jī)總共擁有1 6 4 M BR A M

          實(shí)際上并不擁有1 6 4 M BR A M。相反,操作系統(tǒng)與C P U相協(xié)調(diào),共同將R A M的各個(gè)部分保存到頁文件中,當(dāng)運(yùn)行的應(yīng)用程序需要時(shí),再將頁文件的各個(gè)部分重新加載到R A M。由于頁文件增加了應(yīng)用程序可以使用的R A M的容量,因此頁文件的使用是視情況而定的。如果沒有頁文件,那么系統(tǒng)就認(rèn)為只有較少的R A M可供應(yīng)用程序使用。但是,我們鼓勵(lì)用戶使用頁文件,這樣他們就能夠運(yùn)行更多的應(yīng)用程序,并且這些應(yīng)用程序能夠?qū)Ω蟮臄?shù)據(jù)集進(jìn)行操作。最好將物理存儲(chǔ)器視為存儲(chǔ)在磁盤驅(qū)動(dòng)器(通常是硬盤驅(qū)動(dòng)器)上的頁文件中的數(shù)據(jù)。這樣,當(dāng)一個(gè)應(yīng)用程序通過調(diào)用Vi r t u a l A l l o c函數(shù),將物理存儲(chǔ)器提交給地址空間的一個(gè)區(qū)域時(shí),地址空間實(shí)際上是從硬盤上的一個(gè)文件中進(jìn)行分配的。系統(tǒng)的頁文件的大小是確定有多少物理存儲(chǔ)器可供應(yīng)用程序使用時(shí)應(yīng)該考慮的最重要的因素, R A M的容量則影響非常小。

          第一種情況中,線程試圖訪問的數(shù)據(jù)是在R A M中。在這種情況下, C P U將數(shù)據(jù)的虛擬內(nèi)存地址映射到內(nèi)存的物理地址中,然后執(zhí)行需要的訪問。線程試圖訪問的數(shù)據(jù)不在R A M中,而是存放在頁文件中的某個(gè)地方。這時(shí),試圖訪問就稱為頁面失效, C P U將把試圖進(jìn)行的訪問通知操作系統(tǒng)。這時(shí)操作系統(tǒng)就尋找R A M中的一個(gè)內(nèi)存空頁。如果找不到空頁,系統(tǒng)必須釋放一個(gè)空頁。如果一個(gè)頁面尚未被修改,系統(tǒng)就可以釋放該頁面。但是,如果系統(tǒng)需要釋放一個(gè)已經(jīng)修改的頁面,那么它必須首先將該頁面從R A M拷貝到頁交換文件中,然后系統(tǒng)進(jìn)入該頁文件,找出需要訪問的數(shù)據(jù)塊,并將數(shù)據(jù)加載到空閑的內(nèi)存頁面。然后,操作系統(tǒng)更新它的用于指明數(shù)據(jù)的虛擬內(nèi)存地址現(xiàn)在已經(jīng)映射到R A M中的相應(yīng)的物理存儲(chǔ)器地址中的表。這時(shí)C P U重新運(yùn)行生成初始頁面失效的指令,但是這次C P U能夠?qū)⑻摂M內(nèi)存地址映射到一個(gè)物理R A M地址,并訪問該數(shù)據(jù)塊。

          當(dāng)閱讀了上一節(jié)后,你必定會(huì)認(rèn)為,如果同時(shí)運(yùn)行許多文件的話,頁文件就可能變得非常大,而且你會(huì)認(rèn)為,每當(dāng)你運(yùn)行一個(gè)程序時(shí),系統(tǒng)必須為進(jìn)程的代碼和數(shù)據(jù)保留地址空間的一些區(qū)域,將物理存儲(chǔ)器提交給這些區(qū)域,然后將代碼和數(shù)據(jù)從硬盤上的程序文件拷貝到頁文件中已提交的物理存儲(chǔ)器中。

          實(shí)際上系統(tǒng)并不進(jìn)行上面所說的這些操作。如果它進(jìn)行這些操作的話,就要花費(fèi)很長(zhǎng)的時(shí)間來加載程序并啟動(dòng)它運(yùn)行。相反,當(dāng)啟動(dòng)一個(gè)應(yīng)用程序的時(shí)候,系統(tǒng)將打開該應(yīng)用程序的. e x e文件,確定該應(yīng)用程序的代碼和數(shù)據(jù)的大小。然后系統(tǒng)要保留一個(gè)地址空間的區(qū)域,并指明與該區(qū)域相關(guān)聯(lián)的物理存儲(chǔ)器是在. e x e文件本身中即系統(tǒng)并不是從頁文件中分配地址空間,而是將. e x e文件的實(shí)際內(nèi)容即映像用作程序的保留地址空間區(qū)域。當(dāng)然,這使應(yīng)用程序的加載非常迅速,并使頁文件能夠保持得非常小

          一、開始之前,讓我們來了解一下Windows中內(nèi)存管理的一些知識(shí):

           

          1. 機(jī)器的物理內(nèi)存由兩部分組成。一部分為機(jī)器的主存RAM,也就是我們內(nèi)存條的大小;另一部分為虛擬內(nèi)存,它就在機(jī)器的硬盤上,以頁文件的形式存在。

           

          2. 每個(gè)進(jìn)程都有自己的虛擬地址空間,對(duì)于具有32位尋址能力的機(jī)器來說,這個(gè)虛擬空間的大小為4GB。現(xiàn)在我們使用的機(jī)器就是4GB

           

          3. 進(jìn)程的4GB虛擬地址空間又可以分成幾個(gè)部分,其中進(jìn)程真正私有的空間少于2GB(這段地址空間被稱作“用戶方式分區(qū)”),其余的2GB多空間都是給操作系統(tǒng)的,且這部分空間被所有的進(jìn)程共享。(參考Windows核心編程Chapter 13

           

          4. 為進(jìn)程“分配內(nèi)存”,這個(gè)概念可以細(xì)化:“保留段地址空間”,“提交一段內(nèi)存空間”,“將內(nèi)存空間映射到主存”。在程序中我們通常所訪問的地址都必須是進(jìn)程地址空間中被保留和提交的那段地址空間。

           

          4.1 “保留段地址空間”:即從進(jìn)程4GB地址空間中保留段地址空間,這個(gè)過程通過VirtualAlloc函數(shù)完成,并把分配類型參數(shù)設(shè)置為MEM_RESERVE。這段空間的起始地址必須是系統(tǒng)分配粒度的整數(shù),大小必須是系統(tǒng)頁面大小的整數(shù)

           

          4.2 “提交一段內(nèi)存空間”:即為進(jìn)程已保留的地址空間映射機(jī)器的物理內(nèi)存,這里要特別注意,所謂物理內(nèi)存一般并不是機(jī)器的主存,而只是機(jī)器的虛擬內(nèi)存。這個(gè)過程同樣又VirtualAlloc完成,只是把分配類型參數(shù)設(shè)置為MEM_COMMIT。這段空間的起始地址和大小都必須是頁面大小的整數(shù)。這樣進(jìn)程的對(duì)應(yīng)被提交的區(qū)域就被映射到機(jī)器的虛擬內(nèi)存上。

           

          4.3 “將內(nèi)存空間映射到主存”:這點(diǎn)很重要,操作系統(tǒng)總是只有在進(jìn)程提交的頁面被訪問時(shí)才將相應(yīng)的頁面加載到主存中,同時(shí)修改進(jìn)程對(duì)應(yīng)頁面的地址空間映射。這時(shí),進(jìn)程的地址空間中的對(duì)應(yīng)區(qū)域才和機(jī)器上的主存對(duì)應(yīng)起來。

           

          Virtual Size

           

                該指標(biāo)記錄了當(dāng)前進(jìn)程申請(qǐng)成功的其虛擬地址空間的總的空間大小,包括DLL/EXE占用的地址和通過VirtualAlloc API ReserveMemory Space數(shù)量。請(qǐng)注意,該指標(biāo)包括保留的地址空間。

           

          Private Bytes

           

                 該指標(biāo)記錄了進(jìn)程用戶方式分區(qū)地址空間中已提交的總的空間大小。無論是直接調(diào)用API申請(qǐng)的內(nèi)存,被Heap Manager申請(qǐng)的內(nèi)存,或者是CLR managed heap,都算在里面。

           

          Working Set

           

                 該指標(biāo)記錄了所有映射到進(jìn)程虛擬地址空間的機(jī)器主存的大小,它不僅僅是用戶方式分區(qū)部分的映射,而是整個(gè)進(jìn)程地址空間的映射。即它同時(shí)包括內(nèi)核方式分區(qū)中映射到機(jī)器主存的部分。由4.3可知,在用戶方式分區(qū)部分只有在進(jìn)程提交的頁面被訪問時(shí)才將相應(yīng)的頁面加載到主存中。而對(duì)于該部分的大小總是系統(tǒng)頁面大小的整數(shù)

           

                 這里有一個(gè)問題,隨著進(jìn)程的不斷運(yùn)行,進(jìn)程被訪問的頁面將可能不斷增加,這是否意味著“Working Set”的大小會(huì)不斷的累加呢?顯然不是。在程序運(yùn)行過程中影響“Working Set”的因素包括:(1) 機(jī)器可用主存的大小 (2) 進(jìn)程本身“Working Set”的大小范圍。當(dāng)機(jī)器的可用主存小于一定值時(shí),系統(tǒng)會(huì)釋放一些老的最近沒有被訪問的頁面,把這些頁面通過交換文件交換到機(jī)器的虛擬內(nèi)存中;當(dāng)Working Set的大小大于該進(jìn)程所設(shè)置的最大值時(shí),同樣會(huì)把一些老的頁面交換到機(jī)器的虛擬內(nèi)存中。當(dāng)這些頁面下次再被訪問時(shí),它們才加載到主存。

           

                 由上可知,Working Set“一定比”Private Bytes“小,因?yàn)樗皇?#8221;Private Bytes“對(duì)應(yīng)的地址空間中被加載到主存的那部分

           

          Page Faults”

           

                 該指標(biāo)和Working Set密切相關(guān),當(dāng)進(jìn)程訪問某個(gè)頁面,而這個(gè)頁面卻不在主存中時(shí),就要發(fā)生一次Page Fault“,即進(jìn)程訪問非”Working Set“中的頁面時(shí),發(fā)生一次”Page Fault“,同時(shí)系統(tǒng)將對(duì)應(yīng)頁面加載到主存中。

           

                 接下來的三個(gè)指標(biāo)是對(duì)Working Set“的細(xì)化:

           

          WS Private“

           

                 該指標(biāo)記錄了進(jìn)程Working Set“中被該進(jìn)程所獨(dú)享的空間大小。

           

          "WS Shareable"

           

                 該指標(biāo)記錄了進(jìn)程Working Set“中能與別的進(jìn)程共享的空間大小

           

          WS Shared“

           

                 該指標(biāo)記錄了進(jìn)程Working Set“中已經(jīng)與別的進(jìn)程共享的空間大小

           

          WS Shareable“和”WS Shared“兩個(gè)指標(biāo)一看令人感到疑惑,因?yàn)榧热?#8221;Working Set“屬于”Private Bytes“中的一部分,而”Private Bytes“是進(jìn)程私有的,為什么會(huì)有”WS Shareable“和”WS Shared“這兩項(xiàng)呢?

           

                 認(rèn)真一想,其實(shí)很容易理解,比如兩個(gè)進(jìn)程都需要同一個(gè)DLL的支持,所以在進(jìn)程運(yùn)行過程中,這個(gè)DLL被映射到了兩個(gè)進(jìn)程的地址空間中,如果這個(gè)DLL的大小為4K,在兩個(gè)進(jìn)程中都要提交4K的虛擬地址空間來映射這個(gè)DLL。當(dāng)?shù)谝粋€(gè)進(jìn)程訪問了這個(gè)DLL時(shí),這個(gè)DLL被加載到機(jī)器主存中,這時(shí),第二個(gè)進(jìn)程也要訪問該DLL,這時(shí),系統(tǒng)就不會(huì)再加載一遍該DLL了,因?yàn)檫@個(gè)DLL已經(jīng)在主存中了。當(dāng)然上面所說的訪問僅僅是讀取的操作,如果這時(shí)候某個(gè)進(jìn)程要修改DLL對(duì)應(yīng)這段地址中的某個(gè)單元時(shí),這時(shí),系統(tǒng)必須為第二個(gè)進(jìn)程分配另外的新頁面,并把要修改位置對(duì)應(yīng)的頁面拷貝的這個(gè)新頁面,同時(shí),第二個(gè)進(jìn)程中的這個(gè)DLL被映射到這個(gè)新頁面上。

           

                 上面的分析中,DLL對(duì)應(yīng)的4K的內(nèi)存在第一個(gè)進(jìn)程中便是WS Shareable“。另外,內(nèi)核方式分區(qū)中的所有代碼都是被所有進(jìn)程共享的,只要一個(gè)進(jìn)程訪問了這些頁面,則在所有的進(jìn)程的Working Set“中都能體現(xiàn)。

           

          三、下面我們來討論一下這些內(nèi)存指標(biāo)與進(jìn)程內(nèi)存消耗之間的關(guān)系

           

                 在計(jì)算機(jī)更新?lián)Q代不斷加速的今天,我們往往很少關(guān)注程序?qū)?nèi)存的消耗,除非程序的內(nèi)存消耗超出了我們的忍受范圍——大量的泄漏、運(yùn)行速度下降等。

           

                 那么,當(dāng)我們?cè)?span id="wmqeeuq" class="GramE">測(cè)進(jìn)程的內(nèi)存使用量時(shí),到底應(yīng)該使用哪個(gè)指標(biāo)能更好的反應(yīng)程序的內(nèi)存消耗呢?由于Windows自帶的Task Manager中的Memory Usage“所對(duì)應(yīng)的指標(biāo)就是”Working Set,所以大部分人認(rèn)為該指標(biāo)能夠很好的反應(yīng)進(jìn)程的內(nèi)存使用量。

           

          在得出結(jié)論之前,讓我們來分析一下以上的這些指標(biāo):

           

          就從Working Set“開始吧。

           

          Working Set“:

           

                 進(jìn)程中被加載到機(jī)器主存的所有頁面大小的。它可細(xì)分為WS Shareable“和”WS Shared“。進(jìn)程訪問頁面不再Working Set“中時(shí),會(huì)發(fā)生一次”Page Fault“且同時(shí)發(fā)生一次主存與虛擬內(nèi)存之間的數(shù)據(jù)交換。綜上所述,我們可以得出結(jié)論:

           

          (a)Working Set“不是進(jìn)程內(nèi)存消耗的全部;

           

          (b)所有進(jìn)程Working Set“的和也不等有機(jī)器主存總的消耗量,因?yàn)榇嬖?#8221;Working Shareable“與別的進(jìn)程共享;

           

          (c)Working Set“太大會(huì)影響機(jī)器的運(yùn)行速度,因?yàn)?#8221;Working Set“太大會(huì)導(dǎo)致機(jī)器的可用主存太少,從而導(dǎo)致將進(jìn)程的老頁面釋放到虛擬內(nèi)存,同時(shí),進(jìn)程”Working Set“中的頁面減少后,使進(jìn)程發(fā)生”Page Fault“的頻率更高。因?yàn)樵谥鞔媾c虛擬內(nèi)存之間交換數(shù)據(jù)需要時(shí)間,所以機(jī)器的運(yùn)行速度要減慢。

           

          (d)Working Set“由于數(shù)據(jù)交換的存在,該指標(biāo)是動(dòng)態(tài)的,在測(cè)量的過程中會(huì)不斷變化。(變化的最小單位為4K

           

                 所以Working Set“指標(biāo)強(qiáng)調(diào)的是進(jìn)程對(duì)機(jī)器主存的消耗,不是進(jìn)程內(nèi)存的全部信息。

           

          "Private Bytes"

           

                 該指標(biāo)包含所有為進(jìn)程提交的內(nèi)存,包括機(jī)器主存和虛擬內(nèi)存,可以認(rèn)為它是進(jìn)程對(duì)物理內(nèi)存消耗且該指標(biāo)相對(duì)來說更加穩(wěn)定。在程序產(chǎn)生內(nèi)存泄漏時(shí),該值一定是不斷上漲的。

           

                 綜上所述,個(gè)人更傾向于使用Private Bytes“來定量進(jìn)程的內(nèi)存消耗和分析進(jìn)程的內(nèi)存泄漏。

          posted on 2008-11-20 11:15 so true 閱讀(23659) 評(píng)論(7)  編輯  收藏 所屬分類: Others

          評(píng)論

          # re: 進(jìn)程的虛擬地址空間  回復(fù)  更多評(píng)論   

          這篇文章是我本人在互聯(lián)網(wǎng)上找到的關(guān)于進(jìn)程地址空間說的最好了了,歡迎來頂作者!這樣的文章真的太珍貴!!!
          2011-03-31 16:13 | yiruirui

          # re: 進(jìn)程的虛擬地址空間  回復(fù)  更多評(píng)論   

          @yiruirui
          好文章,收藏了,好好學(xué)習(xí)一下.
          2011-11-01 08:45 | scient

          # re: 進(jìn)程的虛擬地址空間  回復(fù)  更多評(píng)論   

          非常好的文章!!!
          2012-02-14 15:52 | shory

          # re: 進(jìn)程的虛擬地址空間  回復(fù)  更多評(píng)論   

          好文章,感謝樓主~
          2012-04-02 09:47 | adaByron

          # re: 進(jìn)程的虛擬地址空間  回復(fù)  更多評(píng)論   

          神,你是怎么煉成的
          2012-05-07 08:49 | readily

          # re: 進(jìn)程的虛擬地址空間[未登錄]  回復(fù)  更多評(píng)論   

          exe通過內(nèi)存文件映射成為進(jìn)程虛擬地址空間。那么exe文件中的 導(dǎo)出表 導(dǎo)入表 等等section是否也映射到虛擬地址空間里面了呢?還是只映射了
          -------------
          程序代碼和數(shù)據(jù)

          其他庫文件

          內(nèi)核虛擬存儲(chǔ)器
          --------------------
          這些東西?
          文件映射都是進(jìn)程地址空間保留了文件中的所有內(nèi)容。
          那么進(jìn)程的地址空間中是否也保留了exe中的所有內(nèi)容?
          2013-04-10 20:19 | kenny

          # re: 進(jìn)程的虛擬地址空間[未登錄]  回復(fù)  更多評(píng)論   

          寫的真是太透徹了
          2013-10-28 22:30 | ccc
          主站蜘蛛池模板: 营口市| 徐水县| 平定县| 石门县| 丰都县| 襄樊市| 栾川县| 旅游| 吉林市| 怀安县| 邵阳县| 沙河市| 巩留县| 齐齐哈尔市| 大悟县| 陇南市| 万荣县| 东乡| 天津市| 建始县| 铜川市| 大洼县| 区。| 霍林郭勒市| 商丘市| 彭山县| 姜堰市| 定襄县| 清远市| 孝感市| 黔西| 金昌市| 德州市| 射阳县| 赤城县| 安吉县| 富川| 汤阴县| 汨罗市| 桑日县| 延吉市|