轉載:https://blog.csdn.net/wolfman125/article/details/56665300html
編譯時控制分頁能力緩存
有時,驅動程序的某些部分必須駐留內存而另外一些能夠被分頁,這就須要一種能控制代碼和數據是否分頁的方法。經過指導編譯器的段分配能夠實現這個目的。在運行時,裝入器經過檢查驅動程序中的段名, 把段放到你指定的內存池中。此外在運行時調用內存管理器的例程也能實現這個目的。網絡
須要注意的是:
--------------------------------------------------------------------------------
(1) Win32 執行文件,包括內核模式驅動程序,在內部都是由一個或多個段組合而成。段能夠包含代碼或數據,一般還會有諸如可讀性、可寫性、共享性、執行性,等等附加屬性。段是指定分頁能力的最小單元。當 裝載一個驅動程序映像時,操做系統把以「page」或「.eda(.edata)」爲段名開頭的段放到分頁池中,除非 HKLM/System/CurrentControlSet/Control/Session Manager/Memory Management中的DisablePagingExecutive值被設置(在這種狀況下,驅動程序佔用的內存不被分頁)。ide
(2) 在Windows 2000中運行Soft-ICE須要用這種方式禁止內核分頁。但這使得把驅動程序代碼或數據誤放到分頁池中所形成的錯誤特別難以查找。若是你使用這種調試器,我推薦你最好使用PAGED_CODE宏和驅動程序檢查器。函數
(3) 使編譯器把代碼放到特定段的傳統方法是使用alloc_text編譯指示。但不是每種編譯器都支持這個編譯指示,判斷DDK中是否認義了 ALLOC_PRAGMA能夠幫助決定可否使用alloc_text編譯指示。這個編譯指示能夠把驅動程序的單獨例程放到特定段中:
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, AddDevice)
#pragma alloc_text(PAGE, DispatchPnp)
...
#endif
上面語句把AddDevice和DispatchPnp函數的代碼放到分頁池中。 性能
(4) 若是某些代碼在驅動程序完成初始化後再也不須要,能夠直接把它插入到INIT段。例如:
#pragma alloc_text(INIT, DriverEntry)spa
加個頭無非就是說明該函數是分在 分頁內存 非分頁內存,初始內存等等...操作系統
例如:
#pragma alloc_text(PAGE, a)
#pragma alloc_text(PAGE, b)
表示函數a和b都運行在分頁內存中,就是有可能被交換到分頁池中,程序中一些高等級,例如dispatch 級別的代碼固然不能運行在分頁內存,這樣每每出現BSOD
另外#pragma alloc_text(INIT, DriverEntry)
像入口函數代碼在驅動程序完成初始化後每每再也不須要,能夠直接把它插入到INIT段。
ps.Microsoft的C/C++編譯器在alloc_text的使用上加了兩個限制:.net
一、該編譯指示必須跟在函數聲明後面而不能在前面。你能夠把驅動程序中的全部函數集中到一個頭文件中,並在包含該頭文件的源文件中,在#include語句的後面使用alloc_text。
二、該編譯指示僅能用於有C鏈接形式的函數。即,它不能用於類成員函數或 C++源文件中未用extern "C"聲明的函數。調試
轉載連接:http://laokaddk.blog.51cto.com/368606/318387/
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, DiskPerfCreate)
#pragma alloc_text (PAGE, DiskPerfAddDevice)
#endif
何謂可分頁和非分頁內存
默認狀況下,內核加載器會加載全部的代碼部分和全局數據到非分頁內存中。並且,加載器是一次加載整個驅動的可執行文件,包括相關的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 和 MmLockPageableCodeSectionByHandle 來鎖定被標誌爲可分頁的代碼段。
能夠調用MmLockPageableDataSection 和 MmLockPageableDataSectionByHandle 來鎖定被標誌爲可分頁的數據段
能夠調用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來釋放分配的內存。
雖然池幫助減小系統內存的碎片,但池仍是有一些不足:
一、 驅動程序必須預先爲池分配內存,這些內存可能會閒置好久形成內存浪費
二、 你對須要的內存的數量必須至關的精確,在不少時候這個很難作到。
三、 當內存需求增大時,能夠擴大池的尺寸,可是卻不能減少池的尺寸,直到重啓系統
lookaside lists
lookaside lists 是NT4.0裏新的特性,它突破了池的限制。
當你調用 ExInitializeNPagedLookasideList 和ExInitializePagedlookasideList初始化 lookaside lists 時不用預先分配內存,相反,只有當你有真正須要內存的時候才分配。
在初始化時,你必須指定列表的深度,表示尺寸的最大值。相關的函數有ExAllocateFromN-
PagedLookasideList 和ExAllocateFromPagedLookasideList。咱們用一個 NPAGED_
LOOKASIDE_LIST或 PAGED_LOOKASIDE_LIST結構變量來保存lookaside lists的狀態,注意這結構必定要從非分頁內存中分配。