一、 何謂可分頁和非分頁內存
??????
默認情況下,內核加載器會加載所有的代碼部分和全局數據到非分頁內存中。而且,加載器是一次加載整個驅動的可執行文件,包括相關的
DLL
。加載后,內核加載器關閉驅動程序文件,甚至你可以刪除當前正在執行的驅動文件。
但是,你可以告訴加載器你希望驅動的哪部分是可分頁,所謂可分頁,就是可能會被換頁出內存(
Page out
)??梢允褂孟旅娴闹噶顏韺崿F:
#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
的狀態,注意這結構一定要從非分頁內存中分配。
PAGED_LOOKASIDE_LIST
typedef struct _MYDATASTRUCT
{
CHAR buffer[64];
} MYDATASTRUCT,*PMYDATASTRUCT;
VOID LookasideTest()
{
#define NUM 50
PMYDATASTRUCT structs[NUM];
PAGED_LOOKASIDE_LIST Lookaside;
ExInitializePagedLookasideList(&Lookaside, NULL, NULL, 0, sizeof(MYDATASTRUCT), '1234', 0);
// 頻繁請求內存
for(int i = 0; i < NUM; i++)
{
?? structs[i] = (PMYDATASTRUCT)ExAllocateFromPagedLookasideList(&Lookaside);
}
// 頻繁釋放內存
for(int i = 0; i < NUM; i++)
{
?? ExFreeToPagedLookasideList(&Lookaside, structs[i]);
?? structs[i] = NULL;
}
ExDeletePagedLookasideList(&Lookaside);
}