何謂可分頁和非分頁記憶體
預設情況下,核心載入器會載入所有的**部分和全域性資料到非分頁記憶體中。而且,載入器是一次載入整個驅動的可執行檔案,包括相關的dll。載入後,核心載入器關閉驅動程式檔案,甚至你可以刪除當前正在執行的驅動檔案。
但是,你可以告訴載入器你希望驅動的哪部分是可分頁,所謂可分頁,就是可能會被換頁出記憶體(page out)。可以使用下面的指令來實現:
#define alloc_pragma
#pragma alloc_text(page, function_name1)
#pragma alloc_text(page, function_name2)
#endif
由 function_namex 指定的函式**將被放置於可分頁記憶體中。
使資料段可分頁,使用下面的編譯指令:
#ifdef alloc_pragma
#pragma data_seg(page)
// define your pageeble data section module here.
#pragma data_seg()
要注意,絕不能讓可能在高的irql級別被呼叫的例程被換出頁面。
可以呼叫mmlockpageablecodesection 和
mmlockpageablecodesection-
byhandle
來鎖定被標誌為可分頁的**段。
可以呼叫mmlockpageabledatasection 和
mmlockpageabledatasectionb-
yhandle
來鎖定被標誌為可分頁的資料段
可以呼叫mmunlockpageableimagesection 來解除被上面列出的函式鎖定的**
或資料段。
可以呼叫mmpageentiredriver 使整個驅動程式可分頁,覆蓋使用編譯指令修飾的段的頁面屬性。
可以呼叫mmresetdriverpaging 把頁面屬性重設回最初描述的屬性。
最後,把那些驅動初始化後不再需要的**自動丟棄可以使用這些編譯指令:
#ifdef alloc_pragma
#pragma alloc_text(init, driverentry)
#pragma alloc_text(init, function_name) // function called by driverentry
#endif
驅動程式在執行時可能需要動態分配記憶體空間,這時你要決定需要的是可分頁還是不可分頁的記憶體。如果你的驅動在執行中訪問記憶體的時候能夠經受頁錯誤,那麼盡量使用可分頁記憶體。
注意:大多數低層磁碟和網路驅動通常不能使用可分頁記憶體,因為他們的**常常在較高的irql等級執行而不允許頁錯誤。但是,檔案系統(通常比磁碟驅動占用更大,更多資源)有時候可從可分頁池中分配一些記憶體。
非分頁內存在整個系統中是乙個有限的資源,其數量依賴於系統使用的型別,和系統可用的物理記憶體。nt提供下面的例程給核心驅動來分配記憶體:
exallocatepool
exallocatepoolwithquota
exallocatepoolwithtag
exallocatepoolwithquotatag
呼叫這些函式來請求記憶體時,必須要指定請求的記憶體的型別:
nonpagedpool 請求分配乙個不可分頁的記憶體
pagedpool 請求分配乙個可分頁的記憶體
如果你在分配的記憶體裡有任何同步結構的話,決不要分配分頁記憶體。
當你的應用訪問記憶體時候可以處理頁錯誤的時候,應該指定這個型別。
nonpagedpoolmustsucceed
在其它方式都失敗時,而你又必須立即得到記憶體的時候可以使用這個標誌型別。注意這種型別的記憶體是極度缺乏的資源,可能不足16k。注意,只有在其它途徑都失敗的時候才使用,如果分配失敗,將會導致系統的
bugcheck
,錯誤**是 must_succeed_pool_empty。
nonpagedpoolcachealigned
這個標誌分配使用資料快取線的尺寸來在cpu特定的邊界對齊的非分頁記憶體。注意這個操作預設是在intel平台上的 nonpagedpool 分配型別。
pagedpoolcachealigned
這個標誌分配使用資料快取線的尺寸來在cpu特定的邊界對齊的分頁記憶體。
nonpagedpoolcachealignedmustsucceed
參考nonpagedpoolmustsucceed 和nonpagedpoolcachealigned
記憶體池分配器初始化了一些列表,每個列表包含一種固定大小的塊。當你使用上面的函式請求記憶體時,例程試圖分配乙個和你請求數量相近的或更大一點的固定大小的塊。但是,如果你要求的數量超過一頁時,或者超過列表中最大塊的大小時,又或者在預先分配的列表中沒有可用的塊的時候,vmm就會從任何適當型別的系統可用的記憶體中分配你請求的數量記憶體給你。
當預先分配的列表空了的時候,vmm會分配至少一頁的記憶體,切分,然後把剩下的資料放進適當的塊列表中。但是,當你請求的非分頁記憶體的數量超過page_size時候,記憶體池分配例程不會切分未使用的部分,這會浪費寶貴的非分頁記憶體。
也可以使用 mmallocatenoncachedmemory 或 mmallocatecontiguousmemory
來分配非分頁或物理連續記憶體。它們通常不使用在檔案系統或者過濾驅動中,而是用於執行池例程或者其它結構。
核心驅動如果重複的分配和釋放小塊的記憶體(小於乙個page_size), 可能導致系統的可用物理記憶體碎片化。這會給系統帶來各種問題,包括降低系統的效能等。有乙個方法可以避免系統碎片化,就是預先分配一塊合理大小的記憶體,然後自已管理,在這個預先分配的塊中分配和釋放小塊的記憶體,但這種方法有可能會浪費核心記憶體。
用池來管理記憶體
上面提到用預先分配一塊合理大小的記憶體來自已管理,可以避免系統記憶體碎片。我們可以用池來管理這塊預先分配的記憶體。必須再次強調,預先分配的記憶體大小必須足夠準確,太大會浪費寶貴的資源。
呼叫 exallocatepool 來分配池使用的記憶體,你要選擇從分頁或者非分頁的池中分配,注意你的記憶體片基址必須在8位元組的邊界對齊。
還要分配和初始化乙個自旋鎖或者使用其它的同步機制來保護對記憶體塊列表的修改。注意不要在比 dispatch_level 更高的irql 等級使用池操作例程,因為在更高的 irql等級不能使用同步結構。
然後定義乙個zone_header結構的全域性變數,用來作為這個池的控制結構,並呼叫exinitializezone來初始化池頭部。然後,就可以通過呼叫exallocatefromzone和
exinterlockedallocatefromzone 來分配自已管理的記憶體塊。這兩個函式的差別在於後者使用了自旋鎖用於操作同步。呼叫exfreetozone 和exinterlockedfreetozone來釋放分配的記憶體。
雖然池幫助減少系統記憶體的碎片,但池還是有一些不足:
1、驅動程式必須預先為池分配記憶體,這些記憶體可能會閒置很久造成記憶體浪費
2、你對需要的記憶體的數量必須相當的精確,在很多時候這個很難做到。
3、當記憶體需求增大時,可以擴大池的尺寸,但是卻不能減小池的尺寸,直到重啟系統。
lookaside lists
lookaside lists 是nt4.0裡新的特性,它突破了池的限制。
當你呼叫 exinitializenpagedlookasidelist 和exinitializepagedlookasidelist初始化 lookaside lists 時不用預先分配記憶體,相反,只有當你有真正需要記憶體的時候才分配。
在初始化時,你必須指定列表的深度,表示尺寸的最大值。相關的函式有exallocatefromn
pagedlookasidelist 和exallocatefrompagedlookasidelist。我們用乙個 npaged_
lookaside_list或 paged_lookaside_list結構變數來儲存lookaside lists的狀態,注意這結構一定要從非分頁記憶體中分配。
參考 《nt檔案系統內幕》
17 何謂可分頁和非分頁記憶體
1 預設情況下,核心載入器會載入所有的 部分和全域性資料到非分頁記憶體中。而且,載入器是一次載入整個驅動的可執行檔案,包括相關的 dll。載入後,核心載入器關閉驅動程式檔案,甚至你可以刪除當前正在執行的驅動檔案。但是,你可以告訴載入器你希望驅動的哪部分是可分頁,所謂可分頁,就是可能會被換頁出記憶體 ...
分頁記憶體,非分頁記憶體
分頁記憶體是低中斷級別的例程可以訪問的。而非分頁記憶體則是各個中斷級別的例程都可以使用的。區別在於 分頁記憶體是虛擬記憶體,在物理上未必總是能得到。作業系統實現虛擬記憶體的主要方法就是通過分頁機制。在win32中,實體地址空間,二維虛擬位址空間和實際記憶體位址是三個不同的概念。作業系統通過段選擇子構...
分頁記憶體和非分頁記憶體區別
在寫驅動的時候,經常要呼叫exallocatepoolwithtag函式分配記憶體,其中第乙個引數可以是如下幾個 nonpagedpool 從非分頁記憶體池中分配記憶體 pagedpool 從分頁記憶體池中分配記憶體 nonpagedpoolmustsucceed 從非分頁記憶體池中分配記憶體,如果...