鎖定可分頁程式碼或資料
除非已開啟所管理的裝置,否則某些核心模式驅動程式,例如序列和平行驅動程式,不需要是記憶體駐留驅動程式。 不過,只要有作用中的連線或埠,管理該埠的一部分驅動程式程式碼就必須駐留在服務裝置上。 未使用埠或連線時,不需要驅動程式程式碼。 相反地,包含系統程式碼、應用程式程式碼或系統分頁檔案之磁片的驅動程式必須一律為記憶體駐留,因為驅動程式會持續在其裝置與系統之間傳輸資料。
偶爾使用的裝置的驅動程式 (例如數據機) 可以在其管理的裝置未使用時釋放系統空間。 如果您放置於單一區段中,則必須駐留在作用中裝置的程式碼,而且如果驅動程式在使用裝置時鎖定記憶體中的程式碼,則可以將這個區段指定為可分頁。 當驅動程式的裝置開啟時,作業系統會將可分頁區段帶入記憶體中,而驅動程式會將其鎖定到該處,直到不再需要為止。
系統 CD 音訊驅動程式程式碼會使用這項技術。 驅動程式的程式碼會根據 CD 裝置製造商分組成可分頁的區段。 特定品牌可能永遠不會出現在指定的系統上。 此外,即使 CD-ROM 存在於系統上,也可能不常存取,因此,依 CD 類型將程式碼分組為可分頁區段,可確保永遠不會載入特定電腦上不存在之裝置的程式碼。 不過,存取裝置時,系統會載入適當 CD 裝置的程式碼。 然後驅動程式會呼叫 MmLockPagableCodeSection 常式,如下所述,在使用其裝置時將其程式碼鎖定到記憶體中。
若要將可分頁程式碼隔離至具名區段,請使用下列編譯器指示詞加以標示:
#pragma alloc_text (PAGE*Xxx,*RoutineName)
可分頁程式碼區段的名稱必須以四個字母 「PAGE」 開頭,後面最多可以接著四個字元 (以 Xxx) 來唯一識別區段。 區段名稱的前四個字母 (為 「PAGE」) 必須大寫。 RoutineName會識別要包含在可分頁區段中的進入點。
驅動程式檔案中可分頁程式碼區段的最短有效名稱就是 PAGE。 例如,下列程式碼範例中的 pragma 指示詞會 RdrCreateConnection
識別為名為 PAGE 的可分頁程式碼區段中的進入點。
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, RdrCreateConnection)
#endif
若要讓可分頁驅動程式程式碼駐留並鎖定在記憶體中,驅動程式會呼叫 MmLockPagableCodeSection,將位址傳遞 (驅動程式常式的進入點,通常是在可分頁程式碼區段中) 。 MmLockPagableCodeSection 鎖定區段的整個內容,其中包含呼叫中所參考的常式。 換句話說,此呼叫會讓每個常式與位於記憶體中且鎖定的相同 PAGEXxx 識別碼相關聯。
MmLockPagableCodeSection 會透過呼叫 mmUnlockPagableImageSection 常式) 或驅動程式必須從程式碼中的其他位置鎖定區段時,傳回要使用的控制碼 (。
驅動程式也可以將很少使用的資料視為可分頁,因此也可以分頁,直到其支援的裝置處於作用中狀態為止。 例如,系統混音器驅動程式會使用可分頁的資料。 混音器裝置沒有與其相關聯的非同步 I/O,因此此驅動程式可以將其資料分頁。
可分頁資料區段的名稱必須以四個字母 「PAGE」 開頭,後面最多可以接著四個字元來唯一識別區段。 區段名稱的前四個字母 (為 「PAGE」) 必須大寫。
避免將相同的名稱指派給程式碼和資料區段。 為了讓原始程式碼更容易閱讀,驅動程式開發人員通常會將名稱 PAGE 指派給可分頁程式碼區段,因為此名稱簡短,而且可能會出現在許多alloc_text pragma 指示詞中。 然後,會將較長的名稱指派給任何可分頁的資料區段 (,例如,data_seg的 PAGEDATA、bss_seg的 PAGEBSS 等) 驅動程式可能需要。
例如,下列程式碼範例中的前兩個 pragma 指示詞會定義兩個可分頁的資料區段:PAGEDATA 和 PAGEBSS。 PAGEDATA 是使用 data_seg pragma 指示詞來宣告,並包含初始化的資料。 PAGEBSS 是使用 bss_seg pragma 指示詞來宣告,並包含未初始化的資料。
#pragma data_seg("PAGEDATA")
#pragma bss_seg("PAGEBSS")
INT Variable1 = 1;
INT Variable2;
CHAR Array1[64*1024] = { 0 };
CHAR Array2[64*1024];
#pragma data_seg()
#pragma bss_seg()
在此程式碼範例中, Variable1
和 Array1
會明確初始化,因此會放在 PAGEDATA 區段中。
Variable2
和 Array2
會隱含地以零初始化,並放在 PAGEBSS 區段中。
將全域變數隱含初始化為零會減少磁片上可執行檔的大小,而且偏好使用明確初始化為零。 除非需要明確零初始化,才能將變數放在特定資料區段中,否則應該避免進行明確零初始化。
若要將資料區段設為記憶體中,並將它鎖定在記憶體中,驅動程式會呼叫 MmLockPagableDataSection,並傳遞出現在可分頁資料區段中的資料項目。 MmLockPagableDataSection 會傳回要用於後續鎖定或解除鎖定要求的控制碼。
若要還原鎖定區段的可分頁狀態,請呼叫 MmUnlockPagableImageSection,並視需要傳遞 MmLockPagableCodeSection 或 MmLockPagableDataSection傳回的控制碼值。 驅動程式的 Unload 常式必須呼叫 MmUnlockPagableImageSection ,以釋放針對可鎖定程式碼和資料區段取得的每個控制碼。
鎖定區段是昂貴的作業,因為記憶體管理員必須先搜尋其載入的模組清單,才能將頁面鎖定到記憶體中。 如果驅動程式在其程式碼中鎖定許多位置的區段,它應該在其初始呼叫MmLockPagableXxx區段之後,使用更有效率的MmLockPagableSectionByHandle。
傳遞至 MmLockPagableSectionByHandle 的控制碼是先前呼叫 MmLockPagableCodeSection 或 mmLockPagableDataSection所傳回的控制碼。
記憶體管理員會維護每個區段控制碼的計數,並在驅動程式呼叫該區段的MmLockPagableXxx時遞增此計數。 呼叫 MmUnlockPagableImageSection 會遞減計數。 雖然任何區段控制碼的計數器都是非零的,但該區段仍會鎖定在記憶體中。
只要載入區段的驅動程式,區段的控制碼就有效。 因此,驅動程式應該只呼叫 MmLockPagableXxx區段 一次。 如果驅動程式需要額外的鎖定呼叫,它應該使用 MmLockPagableSectionByHandle。
如果驅動程式針對已鎖定的區段呼叫任何MmLockPagableXxx常式,記憶體管理員就會遞增區段的參考計數。 如果在呼叫鎖定常式時分頁區段,則區段中的記憶體管理員頁面會將其參考計數設定為一。
使用這項技術可將驅動程式對系統資源的影響降到最低。 當驅動程式執行時,它可以鎖定必須駐留的程式碼和資料記憶體。 當其裝置沒有未完成的 I/O 要求時, (亦即當裝置關閉或從未開啟裝置) 時,驅動程式可以解除鎖定相同的程式碼或資料,使其可供分頁。
不過,在驅動程式連接中斷之後,任何可在中斷處理期間呼叫的驅動程式程式碼一律必須是記憶體駐留。 雖然某些設備磁碟機可依需求可分頁或鎖定到記憶體中,但這類驅動程式程式碼和資料的某些核心集必須永久保留在系統空間中。
請考慮下列鎖定程式碼或資料區段的實作指導方針。
Mm (Un) LockXxx常式的主要用途是啟用一般非分頁程式碼或資料可分頁,並帶入為非分頁程式碼或資料。 序列驅動程式和平行驅動程式之類的驅動程式是不錯的範例:如果裝置沒有開啟的控制碼,驅動程式會管理、不需要部分程式碼,而且可以保持分頁。重新導向器和伺服器也是可以使用這項技術的驅動程式範例。 沒有作用中的連線時,這兩個元件都可以分頁。
整個可分頁區段會鎖定在記憶體中。
程式碼的一個區段和每個驅動程式的資料區段是有效率的。 許多具名、可分頁的區段通常沒有效率。
將純可分頁的區段和分頁保留,但依需求鎖定的區段分開。
請記住,不應該經常呼叫 MmLockPagableCodeSection 和 mmLockPagableDataSection 。 當記憶體管理員載入 區段時,這些常式可能會導致大量 I/O 活動。 如果驅動程式必須在程式碼中鎖定來自數個位置的區段,它應該使用 MmLockPagableSectionByHandle。