第15章 在應用程序中使用虛擬內存

 Microsoft Windows提供以下三種機制來對內存進行操控:
  • 虛擬內存 :最適合用來管理大型對象數組或大型結構數組。
  • 內存映射文件 :最適合用來管理大型數據流(通常是文件),以及在同一機器上運行的多個進程之間共享數據。
  •  :最適合用來管理大量的小型對象。    

   以下將討論第一種方式,即虛擬內存。 
1.預定地址空間區域 
    我們可以調用VirtualAlloc函數來預訂進程中的地址空間區域: 
    PVOID VirtualAlloc(
        PVOID pvAddress,
        SIZE_T dwSize,
        DWORD fdwAllocationType,
        DWORD fdwProtect);
 

    第一個參數 pvAddress , 是內存地址,用來告訴系統我們想要預定地址空間中的哪一塊。由於系統會記錄所有閒置的地址區間,因此大多數時候我們只要給該參數傳NULL就可以了。這等於是告訴系統自動找一塊閒置區域。記住:這裏的內存地址指的是進程的地址空間中的用戶模式地址空間。 
    第二個參數 dwSize 用來指定我們想要預定的區域大小,以字節爲單位。系統始終都根據CPU頁面大小的整數倍來預訂區域,如我們預定5KB的空間,那麼最終得到的會是8KB(兩個頁面2*4KB)。
     第三個參數
 fdwAllocationType ,用來告訴系統是要預定區域還是要調撥物理存儲器(因爲調撥物理存儲器也是用的VirtualAlloc函數,MEM_RESERVE:預定地址空間區域,MEM_COMMIT:調撥物理存儲器,MEM_TOP_DOWN:指定從儘可能高的內存地址來預訂區域)。 
    第四個參數 fdwProtect 是給區域指定的保護屬性。區域的保護屬性對調撥給該區域的物理存儲器不起任何作用。無論爲區域指定什麼保護屬性,只要還沒有給它調撥物理存儲器,試圖訪問區域內的任何內存地址都會引發訪問違規。
     當區域的保護屬性和將要調撥的物理存儲器的保護屬性相一致時,系統內部的處理效率會更高。 

表1:內存頁面保護屬性 
保護屬性
描述

PAGE_NOACCESS

試圖讀取頁面、寫入頁面或執行頁面中的代碼將引發訪問違規。

PAGE_READONLY

試圖寫入頁面或執行頁面中的代碼將引發訪問違規。

PAGE_READWRITE

試圖執行頁面中的代碼將引發訪問違規。

PAGE_EXECUTE

試圖讀取頁面或寫入頁面將引發訪問違規。

PAGE_EXECUTE_READ

試圖寫入頁面將引發訪問違規。

PAGE_EXECUTE_READWRITE

對頁面執行任何操作都不會引發訪問違規 

PAGE_WRITECOPY

試圖執行頁面中的代碼將引發訪問違規。試圖寫入頁面將使系統爲進程

單獨創建一份該頁面的私有副本(以頁交換文件爲後備存儲器)

PAGE_EXECUTE_WRITECOPY

對頁面執行任何操作都不會引發訪問違規。試圖寫入頁面將使系統爲

進程單獨創建一份該頁面的私有副本( 以頁交換文件爲後備存儲器 

2.給區域調撥物理存儲器 
    在預定了區域之後,我們還需要給區域調撥物理存儲器,這樣才能訪問其中的內存地址。系統會從頁交換文件中來調撥物理存儲器給區域。在調撥物理存儲器時,起始地址始終都是頁面大小的整數倍,整個大小也是頁面大小的整數倍。
     調撥物理存儲器,我們同樣使用VirtualAlloc。但這次我們給參數
 fdwAllocationType 傳MEM_COMMIT。在給物理存儲器指定保護屬性的時候,通常我們使用的保護屬性會和預定區域時相同(大多數情況下是PAGE_READWRITE)。
     在已預訂的區域中,我們必須告訴VirtualAlloc要調撥多少物理存儲器給哪裏。這是通過
pvAddress 和 dwSize 來指定的。前者表示想要調撥物理存儲器給哪個內存地址,後者表示物理存儲器的數量,以字節爲單位。值得注意的是,我們無須一下子給整個區域調撥物理存儲器。 由於系統是基於整個頁面來指定保護屬性的,因此不可能出現同物理存儲頁面有不同保護屬性的情況。但是,同一區域的不同頁面可以有不同的保護屬性。 
3.同時預定和調撥物理存儲器 
    有時我們想同時預定區域並給區域調撥物理存儲器。只需調用一次VirtualAlloc一次就能達到這一目的,如下所示: 
    PVOID pvMem = VirtualAlloc(NULL, 99 * 1024, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 
    函數將分配一個100KB(25*4KB)的區域併爲其調撥物理存儲器。 
    Windows還提供大頁面支持,可以在處理大塊內存的時候提升性能。這種情況下,系統分配內存時,不再使用GetSystemInfo函數返回的SYSTEM_INFO結構中的dwPageSize字段來作爲分配粒度,而是使用下面的函數返回的大頁面分配粒度:
     
SIZE_T GetLargePageMinimum(); 
    注意:如果CPU不支持大頁面分配,那麼GetLargePageMinimum會返回0.

  • 要分配的內存塊的大小(即dwSize參數的值),必須是 GetLargePageMinimum函數返回值的整數倍。
  • 在調用VirtualAlloc時,必須把MEM_RESERVE | MEM_COMMIT與MEM_LARGE_PAGE參數按位或起來。換句話說,我們必須同時預定和 調撥內存,我們不能先預定一塊區域然後再給其中的一部分調撥物理存儲器。
  • 在用VirtualAlloc分配內存時必須傳PAGE_READWRITE保護屬性給fdwProtect參數。   

    Windows認爲用MEM_LARGE_PAGE標誌分配到的內存是不可換頁的(unpagable):也就是說必須駐留在內存中。這也是爲什麼這種方式得到的內存能提供更好的性能。 
4.何時調撥物理存儲器 
    有以下四種方法可以用來確定是否需要給區域中的某一部分調撥物理存儲器:

  • 總 是嘗試調撥物理存儲器。這種方法每次調用VirtualAlloc函數,嘗試去調撥物理存儲器。系統會先檢查是否已經調撥了物理存儲器,如果是,它將不會 再次調撥額外的物理存儲器。它的缺點是,每次都要調用多一次函數調用,而不管是否已經調撥了物理存儲器,這就降低了性能。
  • 使用VirtualQuery函數來判斷是否調撥了物理存儲器。這方法更糟,額外的調用了VirtualQuery。
  • 記錄哪些頁面已經調撥而哪些頁面尚未調撥。這可能非常簡單,也可能機器複雜。
  • 使用結構化異常處理(structured exception handling,SEH)——最佳方案。SEH是操作系統的一項特性,它可以讓系統在發生某種情況時通知我們的應用程序。    

5.撤銷調撥物理存儲器及釋放區域 
    要撤銷調撥給區域的物理存儲器,或是釋放地址空間中的一整塊區域,可以調用VirtualFree函數:
     
BOOL VirtualFree( 
        LPVOID pvAddress, 
        SIZE_T dwSize, 
        DWORD fdwFreeType); 

    用VirtualFree來釋放已預訂區域,那麼只需調用VirtualFree一次,就能夠釋放整個區域以及調撥給該區域的物理存儲器。在調用時,pvAddress 參數必須是區域的基地址。該地址就是預訂區域時VirtualAlloc返回的地址。給定一個內存地址,系統可以知道位於該地址的區域的大小,由於這個原因我們可以傳0給dwSize 參數。實際上,我們必須傳0給dwSize ,否則VirtualFree會失敗。我們必須傳MEM_RELEASE給第三個參數fdwFreeType ,來告訴系統撤銷調撥給該區域的所有物理存儲器,並釋放區域。當釋放區域時,我們必須釋放爲該區域預訂的所有地址空間。 
    如果向撤銷調撥給區域的一部分物理存儲器,但又不想釋放整個區域,那麼我們也還是調用VirtualFree函數。我們必須傳一個內存地址給pvAddress 參數,用來告訴系統要撤銷調撥的第一個頁面的地址。我們在dwSize 參數中指定想要釋放的物理存儲器的大小,並傳MEM_DECOMMIT給fdwFreeType 參數。 
    如果dwSize 爲0,而 pvAddress 又是區域的基地址,那麼VirturalFree會撤銷調撥給該區域的所有頁面。 
何時撤銷調撥物理存儲器 
    在實踐中,想知道何時能夠安全地撤銷調撥物理存儲器是需要一些技巧的。

  • 最簡單的方法是將結構設計成正好等於一個頁面的大小。
  • 另一種更爲實用的解決方案是記錄哪些結構正在使用。
  • 最後一種解決方案是實現一個垃圾收集函數。   

6.改變保護屬性 
    雖然在實際中很少需要改變已調撥的物理存儲頁的保護屬性,但這樣做仍然是可行的。
     我們可以調用VirtualProtect函數來改變一個內存頁面的保護屬性:
     
BOOL VirtualProtect(
    PVOID pvAddress,
    SIZE_T dwSize,
    DWORD flNewProtect,
    PDWORD pflOldProtect);
 

    保護屬性是與整個物理存儲頁相關聯的,我們不能給一個字節指定保護屬性。 
7.重置物理存儲器的內容 
    爲了滿足最近的載入請求,系統會在內存中查找可用的頁面,如果找到的頁面已經被修改過,那麼系統還必須將它們換出到頁交換文件中。 
    Windows提供了一項特性,使得應用程序能夠提高自身的性能——這項特性就是重置物理存儲器 。重置物理存儲器的意思就是,我們告訴系統一個或幾個物理存儲頁中的數據沒有被修改過。如果系統正在查找一頁閒置內存並且找到了一個修改過的頁面,那麼系統必須把該內存頁寫入到頁交換文件中。這個操作比較慢,會影響性能。 對大多數應用程序來說,我們都希望系統把修改後的頁面保存到頁交換文件中。 
    但 是,有些應用程序只需要在一小段時間內使用存儲器,之後也不需要保留存儲器中的內容。爲了提高性能,應用程序可以告訴系統不要在頁交換文件中保存指定的存 儲頁。因此,如果系統決定將一個內存頁挪作它用,那麼它不會將頁面的內容保存到頁交換文件中,這樣提高了性能。爲了重置存儲器,應用程序應該調用 VirtualAlloc函數,並在第三個參數中傳MEM_RESET標誌。 
8.地址窗口擴展 
    隨着時間的推移,應用程序需要越來越多的內存。爲了提高性能,服務器應用程序需要在內存中保持更多的數據以減少磁盤和內存間的頁交換。
     爲了幫助這些應用程序,Windows提供了一項特性,即地址窗口擴展(Address Windowsing Extention,AWE)。創建AWE時,Microsoft有以下兩個目標:

  • 允許應用程序以一種特殊的方式分配內存,操作系統保證不會將以這種方式分配的內存換出到磁盤上。
  • 允許應用程序訪問比進程地址空間還要多的內存。    

    程 序通過調用VirtualAlloc函數來預訂地址窗口。MEM_PHYSICAL標誌表示該區域最終會以物理內存作爲後備。AWE的一個限制是所有映射 到地址窗口的存儲器必須是可讀/寫的。因此,PAGE_READWRITE是我們能傳給VirtualAlloc的唯一有效地保護屬性。 
    分配物理存儲器,調用AllocateUserPhysicalPages函數:
     
BOOL AllocateUserPhysicalPages( 
        HANDLE hProcess, 
        PULONG_PTR pulRAMPages, 
        PULONG_PTR aRAMPages); 

    這個函數會根據pulRAMPages 參數所指向的值來分配相應數量的內存頁,然後將這些頁面分配給hProcess 參數所標識的進程。操作系統會給每個頁面指定一個頁框號。 系統在分配內存頁面時,會將每個內存頁面的頁框號保存到aRAMPages 參數所指向的數組中。 當該函數返回時,pulRAMPages 指向 的值表示函數成功分配的頁面的數量,通常這個值和傳給該函數的值相同,但也有可能更小。 
    創建一個地址窗口並分配一塊內存之後,接下來調用MapUserPhysicalPages把內存塊指定給地址窗口。
     
BOOL MapUserPhysicalPages( 
        PVOID pvAddressWindow,//地址窗口的虛擬地址 
        ULONG_PTR ulRAMPages,//要通過該地址窗口看到多少個頁面的內存 
        PULONG_PTR aRAMPages);表示要通過該地址窗口看到哪些頁面的內存 

    不再需要使用該內存塊時,可以調用FreeUserPhysicalPages來釋放它:
     
BOOL FreeUserPhysicalPages(
    HANDLE hProcess,
    PULONG_PTR pulRAMPages,
    PULONG_PTR aRAMPages);
 

本篇文章出自:http://blog.csdn.net/shenzi/article/details/4671988

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章