一、 何謂可分頁和非分頁內(nèi)存
??????
默認(rèn)情況下,內(nèi)核加載器會(huì)加載所有的代碼部分和全局?jǐn)?shù)據(jù)到非分頁內(nèi)存中。而且,加載器是一次加載整個(gè)驅(qū)動(dòng)的可執(zhí)行文件,包括相關(guān)的
DLL
。加載后,內(nèi)核加載器關(guān)閉驅(qū)動(dòng)程序文件,甚至你可以刪除當(dāng)前正在執(zhí)行的驅(qū)動(dòng)文件。
但是,你可以告訴加載器你希望驅(qū)動(dòng)的哪部分是可分頁,所謂可分頁,就是可能會(huì)被換頁出內(nèi)存(
Page out
)。可以使用下面的指令來實(shí)現(xiàn):
#define ALLOC_PRAGMA
#pragma alloc_text(PAGE, function_name1)
#pragma alloc_text(PAGE, function_name2)
#endif
??????
??????
由
function_namex
指定的函數(shù)代碼將被放置于可分頁內(nèi)存中。
使數(shù)據(jù)段可分頁,使用下面的編譯指令:
#ifdef ALLOC_PRAGMA
#pragma data_seg(PAGE)
//
define your pageeble data section module here.
#pragma data_seg()
要注意,絕不能讓可能在高的
IRQL
級(jí)別被調(diào)用的例程被換出頁面。
??????
可以調(diào)用
MmLockPageableCodeSection
和
MmLockPageableCodeSection-
ByHandle
來鎖定被標(biāo)志為可分頁的代碼段。
可以調(diào)用
MmLockPageableDataSection
和
MmLockPageableDataSectionB-
yHandle
來鎖定被標(biāo)志為可分頁的數(shù)據(jù)段
可以調(diào)用
MmUnlockPageableImageSection
來解除被上面列出的函數(shù)鎖定的代碼
或數(shù)據(jù)段。
可以調(diào)用
MmPageEntireDriver
使整個(gè)驅(qū)動(dòng)程序可分頁,覆蓋使用編譯指令修飾的段的頁面屬性。
可以調(diào)用
MmResetDriverPaging
把頁面屬性重設(shè)回最初描述的屬性。
??????
最后,把那些驅(qū)動(dòng)初始化后不再需要的代碼自動(dòng)丟棄可以使用這些編譯指令:
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(INIT, function_name) // function called by driverEntry
#endif
驅(qū)動(dòng)程序在執(zhí)行時(shí)可能需要?jiǎng)討B(tài)分配內(nèi)存空間,這時(shí)你要決定需要的是可分頁還是不可分頁的內(nèi)存。如果你的驅(qū)動(dòng)在運(yùn)行中訪問內(nèi)存的時(shí)候能夠經(jīng)受頁錯(cuò)誤,那么盡量使用可分頁內(nèi)存。
注意:大多數(shù)低層磁盤和網(wǎng)絡(luò)驅(qū)動(dòng)通常不能使用可分頁內(nèi)存,因?yàn)樗麄兊拇a常常在較高的
IRQL
等級(jí)執(zhí)行而不允許頁錯(cuò)誤。但是,文件系統(tǒng)(通常比磁盤驅(qū)動(dòng)占用更大,更多資源)有時(shí)候可從可分頁池中分配一些內(nèi)存。
??????
非分頁內(nèi)存在整個(gè)系統(tǒng)中是一個(gè)有限的資源,其數(shù)量依賴于系統(tǒng)使用的類型,和系統(tǒng)可用的物理內(nèi)存。
NT
提供下面的例程給內(nèi)核驅(qū)動(dòng)來分配內(nèi)存:
ExAllocatePool
ExAllocatePoolWithQuota
ExAllocatePoolWithTag
ExAllocatePoolWithQuotaTag
調(diào)用這些函數(shù)來請(qǐng)求內(nèi)存時(shí),必須要指定請(qǐng)求的內(nèi)存的類型:
NonPagedPool???
請(qǐng)求分配一個(gè)不可分頁的內(nèi)存
PagedPool??????
請(qǐng)求分配一個(gè)可分頁的內(nèi)存
??????????
如果你在分配的內(nèi)存里有任何同步結(jié)構(gòu)的話,決不要分配分頁內(nèi)存。
??????????
當(dāng)你的應(yīng)用訪問內(nèi)存時(shí)候可以處理頁錯(cuò)誤的時(shí)候,應(yīng)該指定這個(gè)類型。
NonPagedPoolMustSucceed
?????????????
在其它方式都失敗時(shí),而你又必須立即得到內(nèi)存的時(shí)候可以使用這個(gè)標(biāo)志
類型。注意這種類型的內(nèi)存是極度缺乏的資源,可能不足
16K
。注意,只
有在其它途徑都失敗的時(shí)候才使用,如果分配失敗,將會(huì)導(dǎo)致系統(tǒng)的
bugcheck
,錯(cuò)誤代碼是
MUST_SUCCEED_POOL_EMPTY
。
NonPagedPoolCacheAligned
??????????
這個(gè)標(biāo)志分配使用數(shù)據(jù)緩存線的尺寸來在
CPU
特定的邊界對(duì)齊的非分頁
內(nèi)存。注意這個(gè)操作默認(rèn)是在
Intel
平臺(tái)上的
NonPagedPool
分配類型。
PagedPoolCacheAligned
??????????
這個(gè)標(biāo)志分配使用數(shù)據(jù)緩存線的尺寸來在
CPU
特定的邊界對(duì)齊的分頁
內(nèi)存。
NonPagedPoolCacheAlignedMustSucceed
??????????
參考
NonPagedPoolMustSucceed
和
NonPagedPoolCacheAligned
??????
內(nèi) 存池分配器初始化了一些列表,每個(gè)列表包含一種固定大小的塊。當(dāng)你使用上面的函數(shù)請(qǐng)求內(nèi)存時(shí),例程試圖分配一個(gè)和你請(qǐng)求數(shù)量相近的或更大一點(diǎn)的固定大小的 塊。但是,如果你要求的數(shù)量超過一頁時(shí),或者超過列表中最大塊的大小時(shí),又或者在預(yù)先分配的列表中沒有可用的塊的時(shí)候,
VMM
就會(huì)從任何適當(dāng)類型的系統(tǒng)可用的內(nèi)存中分配你請(qǐng)求的數(shù)量?jī)?nèi)存給你。
??????
當(dāng)預(yù)先分配的列表空了的時(shí)候,
VMM
會(huì)分配至少一頁的內(nèi)存,切分,然后把剩下的數(shù)據(jù)放進(jìn)適當(dāng)?shù)膲K列表中。但是,當(dāng)你請(qǐng)求的非分頁內(nèi)存的數(shù)量超過
PAGE_SIZE
時(shí)候,內(nèi)存池分配例程不會(huì)切分未使用的部分,這會(huì)浪費(fèi)寶貴的非分頁內(nèi)存。
也可以使用
MmAllocateNonCachedMemory
或
MmAllocateContiguousMemory
來分配非分頁或物理連續(xù)內(nèi)存。它們通常不使用在文件系統(tǒng)或者過濾驅(qū)動(dòng)中,而是用于執(zhí)行池例程或者其它結(jié)構(gòu)。
??????
內(nèi)核驅(qū)動(dòng)如果重復(fù)的分配和釋放小塊的內(nèi)存(小于一個(gè)
PAGE_SIZE
)
,
可能導(dǎo)致系統(tǒng)的可用物理內(nèi)存碎片化。這會(huì)給系統(tǒng)帶來各種問題,包括降低系統(tǒng)的性能等。有一個(gè)方法可以避免系統(tǒng)碎片化,就是預(yù)先分配一塊合理大小的內(nèi)存,然后自已管理,在這個(gè)預(yù)先分配的塊中分配和釋放小塊的內(nèi)存,但這種方法有可能會(huì)浪費(fèi)核心內(nèi)存。
二、用池來管理內(nèi)存
??????
上面提到用預(yù)先分配一塊合理大小的內(nèi)存來自已管理,可以避免系統(tǒng)內(nèi)存碎片。我們可以用池來管理這塊預(yù)先分配的內(nèi)存。必須再次強(qiáng)調(diào),預(yù)先分配的內(nèi)存大小必須足夠準(zhǔn)確,太大會(huì)浪費(fèi)寶貴的資源。
??????
調(diào)用
ExAllocatePool
來分配池使用的內(nèi)存,你要選擇從分頁或者非分頁的池中分配,注意你的內(nèi)存片基址必須在
8
字節(jié)的邊界對(duì)齊。
??????
還要分配和初始化一個(gè)自旋鎖或者使用其它的同步機(jī)制來保護(hù)對(duì)內(nèi)存塊列表的修改。注意不要在比
DISPATCH_LEVEL
更高的
IRQL
等級(jí)使用池操作例程,因?yàn)樵诟叩?/span>
IRQL
等級(jí)不能使用同步結(jié)構(gòu)。
??????
然后定義一個(gè)
ZONE_HEADER
結(jié)構(gòu)的全局變量,用來作為這個(gè)池的控制結(jié)構(gòu),并調(diào)用
ExInitializeZone
來初始化池頭部。然后,就可以通過調(diào)用
ExAllocateFromZone
和
ExInterlockedAllocateFromZone
來分配自已管理的內(nèi)存塊。這兩個(gè)函數(shù)的差別在于后者使用了自旋鎖用于操作同步。調(diào)用
ExFreeToZone
和
ExInterlockedFreeToZone
來釋放分配的內(nèi)存。
??????
雖然池幫助減少系統(tǒng)內(nèi)存的碎片,但池還是有一些不足:
1、
驅(qū)動(dòng)程序必須預(yù)先為池分配內(nèi)存,這些內(nèi)存可能會(huì)閑置很久造成內(nèi)存浪費(fèi)
2、
你對(duì)需要的內(nèi)存的數(shù)量必須相當(dāng)?shù)木_,在很多時(shí)候這個(gè)很難做到。
3、
當(dāng)內(nèi)存需求增大時(shí),可以擴(kuò)大池的尺寸,但是卻不能減小池的尺寸,直到重啟系統(tǒng)
lookaside lists
lookaside lists
是
NT4.0
里新的特性,它突破了池的限制。
??????
當(dāng)你調(diào)用
ExInitializeNPagedLookasideList
和
ExInitializePagedlookasideList
初始化
lookaside lists
時(shí)不用預(yù)先分配內(nèi)存,相反,只有當(dāng)你有真正需要內(nèi)存的時(shí)候才分配。
在初始化時(shí),你必須指定列表的深度,表示尺寸的最大值。相關(guān)的函數(shù)有
ExAllocateFromN-
PagedLookasideList
和
ExAllocateFromPagedLookasideList
。我們用一個(gè)
NPAGED_
LOOKASIDE_LIST
或
PAGED_LOOKASIDE_LIST
結(jié)構(gòu)變量來保存
lookaside lists
的狀態(tài),注意這結(jié)構(gòu)一定要從非分頁內(nèi)存中分配。
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);
// 頻繁請(qǐng)求內(nèi)存
for(int i = 0; i < NUM; i++)
{
?? structs[i] = (PMYDATASTRUCT)ExAllocateFromPagedLookasideList(&Lookaside);
}
// 頻繁釋放內(nèi)存
for(int i = 0; i < NUM; i++)
{
?? ExFreeToPagedLookasideList(&Lookaside, structs[i]);
?? structs[i] = NULL;
}
ExDeletePagedLookasideList(&Lookaside);
}