標籤:
百度標題中的兩部分,可以找到很多文章,現將收集到的其中兩篇整理如下:
轉載連結:http://hi.baidu.com/billbeggar/item/c378e2ea39a5daeffa42bada
編譯時間控制分頁能力
有時,驅動程式的某些部分必須駐留記憶體而另一些可以被分頁,這就需要一種能控制碼和資料是否分頁的方法。通過指導編譯器的段分配可以實現這個目的。在運行時,裝入器通過檢查驅動程式中的段名, 把段放到你指定的記憶體池中。此外在運行時調用記憶體管理器的常式也能實現這個目的。
需要注意的是:
--------------------------------------------------------------------------------
(1) Win32 執行檔案,包括核心模式驅動程式,在內部都是由一個或多個段組合而成。段可以包含代碼或資料,通常還會有諸如可讀性、可寫性、共用性、執行性,等等附加屬性。段是指定分頁能力的最小單元。當裝載一個驅動程式映像時,作業系統把以“page”或“.eda(.edata)”為段名開頭的段放到分頁池中,除非 HKLM/System/CurrentControlSet/Control/Session Manager/Memory Management中的DisablePagingExecutive值被設定(在這種情況下,驅動程式佔用的記憶體不被分頁)。
(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)
加個頭無非就是說明該函數是分在 分頁記憶體 非分頁式記憶體,初始記憶體等等...
例如:
#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的使用上加了兩個限制:
1、該編譯指示必須跟在函式宣告後面而不能在前面。你可以把驅動程式中的所有函數集中到一個標頭檔中,並在包含該標頭檔的源檔案中,在#include語句的後面使用alloc_text。
2、該編譯指示僅能用於有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來釋放分配的記憶體。
雖然池協助減少系統記憶體的片段,但池還是有一些不足:
1、 驅動程式必須預先為池分配記憶體,這些記憶體可能會閑置很久造成記憶體浪費
2、 你對需要的記憶體的數量必須相當的精確,在很多時候這個很難做到。
3、 當記憶體需求增大時,可以擴大池的尺寸,但是卻不能減小池的尺寸,直到重啟系統
lookaside lists
lookaside lists 是NT4.0裡新的特性,它突破了池的限制。
當你調用 ExInitializeNPagedLookasideList 和ExInitializePagedlookasideList初始化 lookaside lists 時不用預先分配記憶體,相反,只有當你有真正需要記憶體的時候才分配。
在初始化時,你必須指定列表的深度,表示尺寸的最大值。相關的函數有ExAllocateFromN-
PagedLookasideList 和ExAllocateFromPagedLookasideList。我們用一個 NPAGED_
LOOKASIDE_LIST或 PAGED_LOOKASIDE_LIST結構變數來儲存lookaside lists的狀態,注意這結構一定要從非分頁式記憶體中分配。
#pragma alloc_text 與 ALLOC_PRAGMA