Team 7 吳吳吳 吳吳吳吳 吳吳吳 、、 吳吳吳 吳吳吳吳 吳吳吳 、、 吳吳吳 吳吳吳吳 吳吳吳 、、 吳吳吳 吳吳吳吳 吳吳吳 、、 odern Operating System Kernels Presentati Topic: Windows Memory Management Department of Computer Science & Information Engineering, National Central University
Jan 03, 2016
Team
7吳藺剛、陳嘉浩、余修丞吳彥諄、吳政樺、黃詩蘋劉乃菀、黃柏軒、姜智文卓卿安、洪國勛、郭星辰
Modern Operating System Kernels PresentationTopic: Windows Memory Management
Department of Computer Science & Information Engineering, National Central University
4.4 記憶體分頁 4.4.3 分頁錯誤處理 4.4.4 Windows 的寫時複製
4.5 實體記憶體管理 4.5.1 PFN 資料庫 4.5.2 實體頁面的狀態變化 4.5.3 實體頁面串列的管理和操作 4.5.4 修改頁面寫出器 4.5.5 行程 / 堆疊交換器 4.5.6 低 / 高記憶體通知
Outline
4.6 工作集管理 4.6.1 Windows 工作集管理員 4.6.2 平衡集管理員
4.7 記憶體監視工具 4.7.1 記憶體監監視工 4.4.4 Windows 的寫時複製
4.8 本章總結
4.4.3 分頁錯誤處理2012/6/13
陳嘉浩
回顧Intel x86 中的 PDE 和 PTE
無效 PTE 和原型 PTE
分頁錯誤處理分頁錯誤的類型錯誤檢查的流程錯誤處理的方法
Outline
Intel x86 的 PDE 和 PTE
5
PDE: Page Directory
Entry
PTE: Page Table Entry
註解
無效 PTE 情形
6
分頁檔轉移 0 0 保護 PFN* 0
31 015 410 912 11
轉移位元
原型位元
位於分頁檔
分頁框架編號(PFN) 1 0 保護 0
31 01510 912 11
轉移位元
原型位元
WU/SCd Wt
234
頁面正在轉移
0 0 0 保護 0 0
31 015 410 912 11
要求一個零頁面
0 0
031 1
未知原因錯誤
PFN: Page Frame Number
稍候在 4.5 章將會介紹
註解
原型 PTE 介紹
7
指向原型 PTE 的無效 PTE
原型 PTE 例子行程 A 分頁目錄
行程 B 分頁目錄
行程 B 分頁表
行程 A 分頁表
記憶體集區的區段物件
分頁檔
實體記憶體
無效
修改自第六組投影片
8
行程 A 分頁目錄
行程 B 分頁目錄
行程 B 分頁表
行程 A 分頁表
記憶體集區的區段物件
分頁檔
實體記憶體
有效無效
假設行程 A 要存取頁面 P2 ,它會發現分頁表中 P2 的 PTE 是無效的,但它會指向原型 PTE ,而原型 PTE 此時也是無效的且指向分頁檔中的頁面,此時記憶體管理員會根據原型 PTE 的指示為 P2 分配一個實體頁面
實體頁面分配完成後,行程 A 分頁表中的 PTE 以及原型 PTE 都會指向該實體分頁,此時行程 A 分頁表中的 PTE 以及原型 PTE 都為有效,而行程 B 分頁表中的 PTE 則保持原狀
當行程 B 要存取頁面 P2 時,它依然會發現自己的 PTE 是無效的,因此它會檢查原型 PTE ,並且發現 P2 已經在實體記憶體中了。接著P2 分頁表中的 PTE 則會重新指向到實體頁面
分頁錯誤種類 無效 PTE
• 頁面位於分頁檔或對應檔案中• 存取一個尚在記憶體中但正在轉移過程中的頁面• 存取一個尚未提交的頁面• 存取一個要求零的頁面
在使用者模式下存取只能在核心的頁面
寫一個唯讀的頁面
寫一個防護的頁面
執行標記為「不可執行」頁面中的程式碼
寫一個標記為「寫時複製」頁面10
解決辦法
將該頁面轉移回到行程工作集或系統工作集
回傳值STATUS_SUCCESS
STATUS_PAGE_FAULT_TRANSITIO
NSTATUS_IN_PAGE_ERROR
解決辦法
申請一個填滿零的頁面,並加入到目前行程的工作集
回傳值STATUS_SUCCESS
STATUS_PAGE_FAULT_DEMAND_ZER
O
STATUS_IN_PAGE_ERROR
解決辦法
分配一個實體頁面並且從硬碟上讀入內容
回傳值STATUS_SUCCESS
STATUS_PAGE_FAULT_PAGING_FIL
E
STATUS_IN_PAGE_ERROR
解決辦法
複製目前行程私有的頁面,並替換原來的頁面
回傳值STATUS_SUCCESS
STATUS_PAGE_FAULT_COPY_ON_WRIT
E
STATUS_IN_PAGE_ERROR回傳值
STATUS_ACCESS_VIOLATIO
N
回傳值
STATUS_PAGE_FAULT_GUARD_PAG
E
分頁錯誤檢查流程 – MmAccessFault() 函式
11
APC: Asynchronous Procedure
Call ,屬於 IRQL 中斷層級,用於實現延遲和異步的過程調用。詳細請參考第 5.2.2 節
註解
分頁錯誤檢查流程 – 系統位址空間
12
分頁檔轉移 0 0 保護 PFN* 0
31 015 410 912 11
轉移位元
原型位元
位於分頁檔
分頁框架編號(PFN) 1 0 保護 0
31 01510 912 11
轉移位元
原型位元
WU/SCd Wt
234
頁面正在轉移
0 0
031 1
未知原因錯誤
分頁錯誤檢查流程 – 使用者位址空間13
分頁錯誤處理方法 – MiDispatchFault() 函式
14
原型 PTE
檢查如果原型 PTE 表示了頁面尚在記憶體中,即原型 PTE 是有效的,或在轉移狀態,則可以先將原型 PTE 變成有效,然後設置實際的 PTE
否則,呼叫 MiResolveProtoPteFault() 函式根據情況進行處理,然後呼叫 MiCompleteProtoPteFault() 函式把已經有效的原型 PTE 影響到真正的 PTE
正在轉移的 PTE
要求零頁面的 PTE
在分頁檔中的 PTE
分頁錯誤處理方法 – MiDispatchFault() 函式
15
原型 PTE
正在轉移的 PTE
呼叫 MiResolveTransitionFault() 函式,把正在轉移的頁面從它所在的串列中移除,並重新設置 PTE ,使它變成一個有效的 PTE
要求零頁面的 PTE
在分頁檔中的 PTE
分頁錯誤處理方法 – MiDispatchFault() 函式
16
原型 PTE
正在轉移的 PTE
要求零頁面的 PTE
呼叫 MiResolveDemandZeroFault() 函式向系統要一個記憶體頁面,然後設置好 PFN 資料庫中對應該頁面的項目,以及出錯虛擬位址的硬體 PTE
在分頁檔中的 PTE
分頁錯誤處理方法 – MiDispatchFault() 函式
17
原型 PTE
正在轉移的 PTE
要求零頁面的 PTE
在分頁檔中的 PTE
呼叫 MiResolvePageFileFault() 函式將出錯頁面的 PTE 設置為正在轉移以及讀取操作正在進行。該函式不會發起 I/O ,而只是填充好這些資訊,放在輸出參數的 ReadBlock 中
WHY?
分頁錯誤處理方法 – MiDispatchFault() 函式
18
各種錯誤的輔助處理函式最後都會回傳結果訊息給 MiDispatchFault() 函式作後續處理,它會根據回傳的指示決定是否需要呼叫IoPageRead() 函式發出 I/O請求,並且針對I/O 成功與否作相關的處理。最後, MiDispatchFault() 也會負責釋放資源以及維護鎖的一致性
小結
19
在本章節裡面,我們探討了:有哪些可能導致分頁錯誤的情形MmAccessFault() 函式如何透過剖析 PDE
和 PTE 判斷分頁錯誤屬於哪一種情形MiDispatchFault() 函式如何針對各種分頁錯
誤情形進行處理
20
2012/6/13
郭星辰姜智文
4.4.4 Windows 的寫時複製
21
現代作業系統的一個重要特性
機制1. 父行程、子行程為獨立的分頁表,但分頁表指向相同
的實體頁面(唯讀)2. 當行程寫數據,記憶體管理員複製頁面,父行程、子
行程擁有私有頁面(可讀寫)
Windows 的寫時複製 (copy-on-write)
22
將複製的動作延遲到真正需要兩個行程分配各自私有頁面的時候,避免不必要的資料複製,減緩了對記憶體的需求
寫時複製的優點
23
記憶體區段物件創建函數 NtCreateSection 和MmCreateSection 參數 SectionPageProtection指定了記憶體區段物件中頁面的保護屬性PAGE_READ ( 讀 )PAGE_READWRITE ( 完全共用,只有一個 )PAGE_WRITECOPY ( 寫時複製 )
實作原理
24
建立記憶體區段物件
NtCreateSection
MmCreateSection
MiSectionInitialization 建立實體記憶體區物件
分頁檔支撐的記憶體集區MiCreatePagingFileM
ap
映像檔記憶體集區MiCreateImageFileM
ap
資料檔案記憶體集區MiCreateDataFileMap
保護屬性由MmCreateSection 參數SectionPageProtection 經MiMakeProtectionMask 函式變化而來, PAGE_WRITECOPY 變為MM_WRITECOPY此函式建立的映像檔記憶體區預設情形皆為寫時複製的,除非SECTION 有特別設定不相容的保護屬性
頁面保護屬性為MM_EXECUTE_READWRITE ,可讀寫、可執行、不可寫時複製
25
PTE 資料結構MMPTE_HARDWARE 中,第 9 位元是一個保留位元, Windows 將他解釋成CopyOnWrite 位元
PTE 分頁表項目
26
階段一: 一個寫時複製的頁面被第一次分配第一次存取頁面時,硬體 PTE 無效MiCompleteProtoPteFault 呼叫巨集來 填充 PTE 的
位元定義中,寫時複製保護屬性為 0x200 ,即 PTE 中的第
9 位元 設置為 1 ,而寫位元 (第 1 位元 ) 設置 0 ,發生分頁錯誤
實作過程 (1/2)
27
階段二:一個行程對一個支援寫時複製的頁面執行了寫入操作,觸發一個分頁錯誤MmAccessFault 檢測錯誤 → 呼叫 MiCopyOnWrite
實作過程 (2/2)
1. 根據參數中指定的出錯位址找到 PFN 資料庫中對應的項,印證他是一個原型 PTE2. 透過 MiRemoveAnyPage申請一個實體頁面3. 呼叫 MiInitializeCopyOnWritePfn初始化其 PFN 對應的項4. 從系統 PTE 區域中申請一個空閒 PTE ,完成記憶體頁面的複製5. 填好出錯位址 PTE 項目6. 舊頁面 PFN 資料庫中計數減一
28
4.5 實體記憶體管理
29
2012/6/13
黃詩蘋姜智文
4.5.1 PFN 資料庫
30
PFN 資料庫union { MMPTE OriginalPte; LONG AweReferenceCount; } ;union { ULONG_PTR EntireFrame; struct { ULONG_PTR PteFrame: 25; ULONG_PTR InPageError : 1; ULONG_PTR VerifierAllocation : 1; ULONG_PTR AweAllocation : 1; ULONG_PTR Priority : MI_PFN_PRIORITY_BITS;
ULONG_PTR MustBeCached : 1; };} u4;} MMPFN, *PMMPFN;
Extern PMMPFN MmPfnDatabase;
# define (index) (&MmPfnDatabase[index])
MI_PFN_ELEMENT
typedef struct _MMPFN { union { PFN_NUMBER Flink; WSLE_NUMBER WsIndex; PKEVENT Event; NTSTATUS ReadStatus; SINGLE_LIST_ENTRY NextStackPfn; } u1; PMMPTE PteAddress; union { PFN_NUMBER Blink; ULONG_PTR ShareCount; } u2; union { struct { USHORT ReferenceCount; MMPFNENTRY e1; } ; struct { USHORT ReferenceCount; USHORT ShortFlags; } e2; } u3;
31
實體頁面的可能狀態 (1/2)1.活動狀態 (active) •頁面正在被某個行程使用,或者被用
於系統空間 (非分頁集區,或者在系 統工作集 )•有效的 PTE 指向頁面
2.備用狀態 (standby) •原屬於某個行程或系統,但現在已從 工作集中移除•頁面的內容尚未被修改•原 PTE 正在轉移的無效 PTE
3. 已修改狀態 (modified)
•頁面的內容已被修改過•原 PTE 正在轉移的無效 PTE
4. 已修改但不寫出 (modified no-write)
•類似於 3. ,但差別在於,記憶體管理 員不會將內容寫到磁碟上
32
實體頁面的可能狀態 (2/2)
5. 轉移狀態•頁面正進行 I/O 操作•若發生分頁錯誤時,可透過此狀態判斷出衝突 的情形
6. 空閒狀態 •頁面是空閒的,不屬於任何一個工作集
7. 零化狀態 •內容歸零,也不屬於任何一個工作集
8. 壞狀態 •產生硬體錯誤
33
請問實體頁面的狀態有哪些呢 ? 請列出五種來吧ˊ _>ˋ
~問題來囉~
34
(a)活動狀態頁面的 PFN項目
union { WSLE_NUMBER WsIndex; } u1; PMMPTE PteAddress; union { ULONG_PTR ShareCount; } u2; union { struct { USHORT ReferenceCount; MMPFNENTRY e1; } ; } u3;MMPTE OriginalPte;union { struct { ULONG_PTR PteFrame: 25; ULONG_PTR InPageError : 1; ULONG_PTR VerifierAllocation : 1; ULONG_PTR AweAllocation : 1; ULONG_PTR Priority : 3; ULONG_PTR MustBeCached : 1; };} u4;
union { PFN_NUMBER Flink; } u1; PMMPTE PteAddress; union { PFN_NUMBER Blink; } u2; union { struct { USHORT ReferenceCount; MMPFNENTRY e1; } ; } u3;MMPTE OriginalPte;union { struct { ULONG_PTR PteFrame: 25; ULONG_PTR InPageError : 1; ULONG_PTR VerifierAllocation : 1; ULONG_PTR AweAllocation : 1; ULONG_PTR Priority : 3; ULONG_PTR MustBeCached : 1; };} u4;
(b)備用狀態或已修改狀態的 PFN項目(c)歸零的或空閒頁面的 PFN項目
union { PKEVENT Event; NTSTATUS ReadStatus; } u1; PMMPTE PteAddress; union { ULONG_PTR ShareCount; } u2; union { struct { USHORT ReferenceCount; MMPFNENTRY e1; } ; } u3;MMPTE OriginalPte;union { struct { ULONG_PTR PteFrame: 25; ULONG_PTR InPageError : 1; ULONG_PTR VerifierAllocation : 1; ULONG_PTR AweAllocation : 1; ULONG_PTR Priority : 3; ULONG_PTR MustBeCached : 1; };} u4;
(d)轉移狀態的 PFN項目
MMPFN 資料結構
35
typedef struct _MMPFNENTRY { USHORT Modified : 1 ; USHORT ReadInProgress : 1 ; USHORT WriteInProgress : 1 ; USHORT PrototypePte : 1 ; USHORT PageColor : 4 ; USHORT PageLocation : 3 ; USHORT RemovalRequested :
1 ; USHORT CacheAttribute : 2 USHORT Rom : 1 ; USHORT ParityError : 1 ;} MMPFNENTRY ;
MMPFNENTRY 定義頁面已被修改
原型 PTE
正進行”讀取”或”寫入”操作
頁面狀態
36
PFN 資料庫和實體頁面串列
37
2012/6/13
余修丞姜智文
4.5.2 實體頁面的狀態變化
38
記憶體管理員根據「系統記憶體的數量」以及各個「行程對於記憶體的需求」,動態調度實體頁面的使用
ex: 當一行程需要記憶體,記憶體管理員可以從「零化串列」或「空閒串列」中找到頁面滿足行程需求
行程結束,記憶體管理員回收行程的頁面
頁面的調度與使用
39
記憶體管理員動態調度的過程,頁面可能會經歷各種變化
實體頁面的狀態轉移
工作集( 行程或系
統 )
歸零串列 空閒串列
備用串列
修改串列
“軟”錯誤
零頁面程序
“軟”錯誤
“軟”錯誤
“硬”錯誤刪除頁面 修剪乾淨頁面
必要時
修剪”髒”頁面
修改頁面寫出器
40
記憶體管理員動態調度的過程,頁面可能會經歷各種變化
實體頁面的狀態轉移
工作集( 行程或系
統 )
歸零串列 空閒串列
備用串列
修改串列
“軟”錯誤
零頁面程序
“軟”錯誤
“軟”錯誤
“硬”錯誤刪除頁面 修剪乾淨頁面
必要時
修剪”髒”頁面
修改頁面寫出器
41
為解決分頁錯誤,行程會需要一個頁面,它有可能從圖中的 4 個串列其中之一取得實體記憶體頁面
行程取得頁面 - 以分頁錯誤為例 -1
歸零串列 空閒串列 備用串列 修改串列
42
從修改或備用串列中取得實體頁面分頁錯誤由正在轉移狀態的無效 PTE引起
行程取得頁面 - 以分頁錯誤為例 -2
PTE
分頁框架編號
1.尋找實體頁面
PFN 資料庫項目
MiUnlinkPageFromList 函式
2.呼叫
備用串列 修改串列
3. 該頁面從串列中移除
43
從空閒 ( 零化或備用 ) 串列取得實體頁面 :分頁錯誤由一位於分頁檔中的 PTE 所引起,分頁錯誤處理常式會透過 MiRemoveAnyPage 函式獲得一實體頁面滿足分頁錯誤。
從零化 ( 空閒或備用 ) 串列取得實體頁面 :若分頁錯誤需要零頁面來滿足此錯誤,則呼叫MiRemoveZeroPage 函式獲得一零化頁面
行程取得頁面 - 以分頁錯誤為例 -3
44
記憶體管理員動態調度的過程,頁面可能會經歷各種變化
實體頁面的狀態轉移
工作集( 行程或系
統 )
歸零串列 空閒串列
備用串列
修改串列
“軟”錯誤
零頁面程序
“軟”錯誤
“軟”錯誤
“硬”錯誤刪除頁面 修剪乾淨頁面
必要時
修剪”髒”頁面
修改頁面寫出器
45
空閒串列 -( 零頁面緒程 )-> 零化串列 - MmZeroPageThread 函式
頁面於串列間的轉移 (1/3)
46
MmZeroPageThread 函式是個無限迴圈,他等待以下兩個事件之一 : MmZeroingPageEvent 和PoSystemIdelTimer
零頁面緒程 (1/2)
零化事件MmZeroingPageEv
ent
2. 空閒串列頁面數大於MmMinimumFreePageToZero= 8
MmZeroPageThread
MiInsertPageInFreeList 函式
空閒串列
3. 通知1.插入頁面
47
零化操作
重複以上步驟,直至目前空閒串列頁面全部零化
零頁面緒程 (2/2)
零化事件MmZeroingPageE
vent
1.通知MmZeroPageThr
ead
MiInsertPageInList
函式空閒串列MmZeroPageListH
ead
3. 呼叫2. 取得頁面設定成壞頁面指定為零
4. 將頁面插入至
48
記憶體管理員動態調度的過程,頁面可能會經歷各種變化
實體頁面的狀態轉移
工作集( 行程或系
統 )
歸零串列 空閒串列
備用串列
修改串列
“軟”錯誤
零頁面程序
“軟”錯誤
“軟”錯誤
“硬”錯誤刪除頁面 修剪乾淨頁面
必要時
修剪”髒”頁面
修改頁面寫出器
49
備用串列 -(必要時 )-> 空閒串列一般情況下,備用串列頁面不會主動地轉移到空閒串列中,除非這些頁面已經確定不被使用了。
發生轉移的例子 :當行程刪除一段記憶體時,包括行程、程序結束時清除記憶體做的操作 ( 發生在 MiDeleteVirtualAddresses 函式中 )
頁面於串列間的轉移 (2/3)
50
MiDeleteVirtualAddresses 函式被呼叫到,會把不再共用的有效頁面轉移到空閒串列中,也就是從工作集刪除頁面,見圖 4.29
此過程發生如下 :
從工作集「刪除頁面」
MiDeleteVirtualAddresses
函式
MiDecrementShareCount 函式MiDeletePte
函式 共用計數器
空閒串列
1. 呼叫
共用計數器減 1為 0引用計數器為 1
2. 呼叫 引用計數器
工作集轉移頁面
51
當可用行程記憶體數量很少時,記憶體管理員修剪行程或系統工作集,這工作由MmWorkingSetManager( 工作集管理員 ) 函式的元件完成
工作集修剪
工作集( 行程或系
統 )
歸零串列 空閒串列
備用串列
修改串列
“軟”錯誤
零頁面程序
“軟”錯誤
“軟”錯誤
“硬”錯誤刪除頁面 修剪乾淨頁面
必要時
修剪”髒”頁面
修改頁面寫出器
52
記憶體管理員動態調度的過程,頁面可能會經歷各種變化
實體頁面的狀態轉移
工作集( 行程或系
統 )
歸零串列 空閒串列
備用串列
修改串列
“軟”錯誤
零頁面程序
“軟”錯誤
“軟”錯誤
“硬”錯誤刪除頁面 修剪乾淨頁面
必要時
修剪”髒”頁面
修改頁面寫出器
53
修改串列 -( 修改頁面寫出器 )-> 備用串列隨著工作集的頁面被加到修改串列中,修改串列會變大。到一定程度,記憶體管理員要將修改串列的頁面寫到磁碟上,進而把頁面轉移到備用串列,是利用修改頁面寫出器來完成
頁面於串列間的轉移 (3/3)
54
2012/6/13
吳藺剛
4.5.3 實體頁面的管理和操作
55
在 MMPFN 資料結構的 u3.e1 成員,有一個 4 位元的 PageColor欄位,代表一個 PFN 項目的顏色
Color 參數 (1/2)
Typedf struct _MMCOLOR_TABLES{PFN_NUMBER Flink;PVOID Blink;PFN_NUMBER Count;
}MMCOLOR_, *PMMCOLOR_TABLES;Extern PMMCOLOR_TABLES MmFreePagesByColor[2];
56
初始化在 MiInitMachineDependent 函式,與MmSecondaryColors 和MmSecondaryColorsMask 有關
例如 MmFreePagesByColor[0][0]代表顏色為 0的零化頁面串列, MmFreePagesByColor[1][0]代表顏色為 0 的空閒頁面串列開頭
Color 參數 (2/2)
57
空閒頁面的插入 (1/2)MiInsertPageInFreeList00638 /* Get the last page on the list */00639 LastPage = ListHead->Blink;00640 if (LastPage != LIST_HEAD)00641 {00642 /* Link us with the previous page, so we're at the end now */00643 MI_PFN_ELEMENT(LastPage)->u1.Flink = PageFrameIndex;00644 }00645 else00646 {00647 /* The list is empty, so we are the first page */00648 ListHead->Flink = PageFrameIndex;00649 }00650 00651 /* Now make the list head point back to us (since we go at the end) */00652 ListHead->Blink = PageFrameIndex;00653 ASSERT_LIST_INVARIANT(ListHead);00654 00655 /* And initialize our own list pointers */00656 Pfn1->u1.Flink = LIST_HEAD;00657 Pfn1->u2.Blink = LastPage;
58
空閒頁面的插入 (2/2)00682 /* Get the page color */00683 Color = PageFrameIndex & MmSecondaryColorMask;00684 00685 /* Get the first page on the color list */00686 ColorTable = &MmFreePagesByColor[FreePageList][Color];00687 if (ColorTable->Flink == LIST_HEAD)00688 {00689 /* The list is empty, so we are the first page */00690 Pfn1->u4.PteFrame = COLORED_LIST_HEAD;00691 ColorTable->Flink = PageFrameIndex;00692 }00693 else00694 {00695 /* Get the previous page */00696 Blink = (PMMPFN)ColorTable->Blink;00697 00698 /* Make it link to us, and link back to it */00699 Blink->OriginalPte.u.Long = PageFrameIndex;00700 Pfn1->u4.PteFrame = MiGetPfnEntryIndex(Blink);00701 }00702 00703 /* Now initialize our own list pointers */00704 ColorTable->Blink = Pfn1;
59
零化頁面的插入 (1/2)MiInsertPageInFreeList00868 /* Zero pages go to the head, all other pages go to the end */00869 if (ListName == ZeroedPageList)00870 {00871 /* Make the head of the list point to this page now */00872 Flink = ListHead->Flink;00873 ListHead->Flink = PageFrameIndex;00874 00875 /* Make the page point to the previous head, and back to the list */00876 Pfn1->u1.Flink = Flink;00877 Pfn1->u2.Blink = LIST_HEAD;00878 00879 /* Was the list empty? */00880 if (Flink != LIST_HEAD)00881 {00882 /* It wasn't, so update the backlink of the previous head page */00883 Pfn2 = MI_PFN_ELEMENT(Flink);00884 Pfn2->u2.Blink = PageFrameIndex;00885 }00886 else00887 {00888 /* It was empty, so have it loop back around to this new page */00889 ListHead->Blink = PageFrameIndex;00890 }
60
零化頁面的插入 (2/2)00941 /* Get the page color */00942 Color = PageFrameIndex & MmSecondaryColorMask;00943 00944 /* Get the list for this color */00945 ColorHead = &MmFreePagesByColor[ZeroedPageList][Color];00946 00947 /* Get the old head */00948 Flink = ColorHead->Flink;00949 00950 /* Make this page point back to the list, and point forwards to the old head */00951 Pfn1->OriginalPte.u.Long = Flink;00952 Pfn1->u4.PteFrame = COLORED_LIST_HEAD;00953 00954 /* Set the new head */00955 ColorHead->Flink = PageFrameIndex;
61
從串列開頭移除一個零化或空閒頁面1. 在 MiRemovePageFromList 函式中,先從指定的串列開頭移除一個頁面,然後更新顏色串列。2. 在 MiRemovePageByColor 函式中,根據指定顏色和頁面 PFN 從串列開頭移除頁面,並更新顏色串列
00364 /* Get the PFN entry */00365 Pfn1 = MI_PFN_ELEMENT(PageIndex);00366 ASSERT(Pfn1->u3.e1.RemovalRequested == 0);00367 ASSERT(Pfn1->u3.e1.Rom == 0);
00379 /* Remove a page */00380 ListHead->Total--;
00414 /* Zero flags but restore color and cache */00415 Pfn1->u3.e2.ShortFlags = 0;00416 Pfn1->u3.e1.PageColor = OldColor;00417 Pfn1->u3.e1.CacheAttribute = OldCache;00418 00419 /* Get the first page on the color list */00420 ASSERT(Color < MmSecondaryColors);00421 ColorTable = &MmFreePagesByColor[ListName][Color];00422 ASSERT(ColorTable->Count >= 1);00423 00424 /* Set the forward link to whoever we were pointing to */00425 ColorTable->Flink = Pfn1->OriginalPte.u.Long;
62
用法:MMPFN 用三位元描述一個頁面的優先順序 0-7 ,有 8 個備用串列飛別存放各個優先順序的頁面EX:MmStandByPageListByPriority
在 WRK 中,備用串列 MmStandbyPageListHead雖然有定義,但並沒有真實用到,真正的備用串列是MmStandybyPageListByPriority陣列
備用串列
63
在串列前端插入備用頁面 (1/2)1.MiInsertStandbyListAtFront00743 /* Grab the PFN and validate it is the right kind of PFN being inserted */00744 Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);00745 ASSERT(Pfn1->u4.MustBeCached == 0);00746 ASSERT(Pfn1->u3.e2.ReferenceCount == 0);00747 ASSERT(Pfn1->u3.e1.PrototypePte == 1);00748 ASSERT(Pfn1->u3.e1.Rom != 1);00749 00750 /* One more transition page on a list */00751 MmTransitionSharedPages++;00752 00753 /* Get the standby page list and increment its count */00754 ListHead = &MmStandbyPageListByPriority [Pfn1->u4.Priority];00755 ASSERT_LIST_INVARIANT(ListHead);00756 ListHead->Total++;00757 00758 /* Make the head of the list point to this page now */00759 Flink = ListHead->Flink;00760 ListHead->Flink = PageFrameIndex;00761 00762 /* Make the page point to the previous head, and back to the list */00763 Pfn1->u1.Flink = Flink;00764 Pfn1->u2.Blink = LIST_HEAD;
64
在串列前端插入備用頁面 (2/2)2. 在 MiInsertPageList 函式中,即使參數中只訂了 MmStandbyPageListHead 作為串列開頭,真正的目標串列開頭也要轉變成 MmStandbyPageKistByPriority 陣列中對應優先順序的串列。
00838 /* Standby pages are prioritized, so we need to get the real head */00839 if (ListHead == &MmStandbyPageListHead)00840 {00841 /* Obviously the prioritized list should still have the same name */00842 ListHead = &MmStandbyPageListByPriority[Pfn1->u4.Priority];00843 ASSERT(ListHead->ListName == ListName);00844 }
65
在串列中移除一個備用頁面在 MiRemovePageFromList 函式中,如果要移除備用頁面,客戶程式碼不能指定 MmStandbyPageListHead 作為備用串列,而必須是MmStandbyPageListByPriority 陣列的一個成員。 MiRestoreTransitionPte 函式中,移除一個節點,對於一個備用頁面,其對應的 PTE 會處於轉移狀態,所以頁面移除後,須恢復對應 PTE 的內容,而不再處於轉移狀態
00254 // Remove page, but leave its status as idle.00255 Pfn->u3.e1.Location = StandbyPageList;0025600257 MiRestoreTransitionPte (PageFrameIndex);00258 MiInsertPageInList (MmPageLocationList[BadPageList],PageFrameNumber);00269 return;
66
在串列中間移除一個備用頁面在 MiUnlinkPageFromList 函式中,如果根據頁面 PFN 項目得到的串列是 MmStandbyPageListHead ,則將串列轉換為MmStandbyPageListByPriority 陣列中對應的串列00247 /* Check if this was standby, or modified */00248 if (ListHead == &MmStandbyPageListHead)00249 {00250 /* Should not be a ROM page */00251 ASSERT(Pfn->u3.e1.Rom == 0);00252 00253 /* Get the exact list */00254 ListHead = &MmStandbyPageListByPriority[Pfn->u4.Priority];00255 00256 /* See if we hit any thresholds */00257 if (MmAvailablePages == MmHighMemoryThreshold)00258 {00259 /* Clear the high memory event */00260 KeClearEvent(MiHighMemoryEvent);00261 }00262 else if (MmAvailablePages == MmLowMemoryThreshold)00263 {00264 /* Signal the low memory event */00265 KeSetEvent(MiLowMemoryEvent, 0, FALSE);00266 }
67
分成兩部分:頁面 PFN 項目的 OriginalPre.u.Soft.Prototype 位元欄位為 0 則該頁面被置於MmModifiedPageListByColor[0] 中,外部記憶體位置是分頁檔
其餘頁面被放在全域串列 MmModifiedPageListHead中,這些頁面的外部記憶體位置是對應檔案
MmModifiedPageListHead 中的計數值也包括位於分頁檔的那些頁面
修改頁面的插入透過 MiInsertPageInList ,移除透過MiUnlinkPageFromList
修改串列
68
該串列的頁面被修改了,但記憶體管理員的修改頁面寫出器並不會把這些頁面寫到外部記憶體
透過 MiInsertPageInList 可在該串列尾部插入頁面,透過 MiInsertFrontNodifiedNoWrite 可在該串列前端插入一個頁面, 頁面移除則要靠MiUnlinkPageFromList 完成
MmEnableModifiedWriteOfSectionU.Flags.NoModiefiedWriting旗標位元
修改但不寫出頁面的串列 (1/2)
69
修改但不寫出頁面的串列 (2/2)MmEnableModifiedWriteOfSection
4657 // Move any straggling pages on the modnowrite list back onto the modified list otherwise they would be orphaned and this could cause us to run out of pages.4658 if (MmModifiedNoWritePageListHead.Total != 0) {4659 PageFrameIndex = MmModifiedNoWritePageListHead.Flink;4660 do {4661 Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);4662 NextPageFrameIndex = Pfn1->u1.Flink;4663 Subsection = MiGetSubsectionAddress (&Pfn1->OriginalPte);4664 if (ControlArea == Subsection->ControlArea) {4665 // This page must be moved to the modified list.4666 MiUnlinkPageFromList (Pfn1);4667 MiInsertPageInList (&MmModifiedPageListHead,PageFrameIndex);4668 }4669 PageFrameIndex = NextPageFrameIndex;4670 } while (PageFrameIndex != MM_EMPTY_LIST);
70
NTFS 檔案系統利用這種頁面來對應檔案系統中繼資料,所以,這些資料不會自動寫上磁碟上,當紀錄中繼資料修改情況的日誌資訊被寫到磁碟上後,才允許中繼資料寫上
保證中繼資料的修改均有紀錄,若發生意外,中繼資料可以恢復
副作用是這些頁面將占據系統實體記憶體,所以客戶有責任確保不會累積太多,且透過MmEnableModifiedWriteOfSection 將他移除
修改但不寫出頁面的經典用途
71
相對簡單,並不參與系統頁面調度,透過MiInsertPageInList 可以將一個壞頁面插入MmBadPageListHead 串列
若零化、空閒或備用頁面的 PFN 項目的u3.e1.RemovalReaquested 位元欄,被設定,則被插入到會頁面串列
壞頁面插入後就不再移除
壞頁面串列
72
MiRemoveZeroPage00549 /* Check the colored zero list */00550 PageIndex = MmFreePagesByColor[ZeroedPageList][Color].Flink;00551 if (PageIndex == LIST_HEAD)00552 {00553 /* Check the zero list */00554 ASSERT_LIST_INVARIANT(&MmZeroedPageListHead);00555 PageIndex = MmZeroedPageListHead.Flink;00556 if (PageIndex == LIST_HEAD)00557 {00558 /* This means there's no zero pages, we have to look for free ones */00559 ASSERT(MmZeroedPageListHead.Total == 0);00560 Zero = TRUE;00561 00562 /* Check the colored free list */00563 PageIndex = MmFreePagesByColor[FreePageList][Color].Flink;00564 if (PageIndex == LIST_HEAD)00565 {00566 /* Check the free list */00567 ASSERT_LIST_INVARIANT(&MmFreePageListHead);00568 PageIndex = MmFreePageListHead.Flink;00569 Color = PageIndex & MmSecondaryColorMask;00570 ASSERT(PageIndex != LIST_HEAD);00571 if (PageIndex == LIST_HEAD)00572 {00573 /* FIXME: Should check the standby list */00574 ASSERT(MmZeroedPageListHead.Total == 0);00575 }00576 }00577 }00578 else00579 {00580 Color = PageIndex & MmSecondaryColorMask;00581 }00582 }
73
MiRemoveAnyPage00492 /* Check the colored free list */00493 PageIndex = MmFreePagesByColor[FreePageList][Color].Flink;00494 if (PageIndex == LIST_HEAD)00495 {00496 /* Check the colored zero list */00497 PageIndex = MmFreePagesByColor[ZeroedPageList][Color].Flink;00498 if (PageIndex == LIST_HEAD)00499 {00500 /* Check the free list */00501 ASSERT_LIST_INVARIANT(&MmFreePageListHead);00502 PageIndex = MmFreePageListHead.Flink;00503 Color = PageIndex & MmSecondaryColorMask;00504 if (PageIndex == LIST_HEAD)00505 {00506 /* Check the zero list */00507 ASSERT_LIST_INVARIANT(&MmZeroedPageListHead);00508 PageIndex = MmZeroedPageListHead.Flink;00509 Color = PageIndex & MmSecondaryColorMask;00510 ASSERT(PageIndex != LIST_HEAD);00511 if (PageIndex == LIST_HEAD)00512 {00513 /* FIXME: Should check the standby list */00514 ASSERT(MmZeroedPageListHead.Total == 0);00515 }
74
Windows 提供用來描述實體記憶的資料結構
存取到包含這些頁面的 PFN
MDL(Memory Descriptor List)
Typedef struct _MDL{Struct _MDL *Next;CSHORT Size;CSHIRT MdlFlags;Struct _EORICESS *Process;POVID MappedSystemVa;ULONG ByteCount;ULONG ByteOffset;
}MDL, *PMDL
Page=(PPFB_NUMBER)(Mdl+1);
75
PMDL
MDL三個介面函式 (1/3)
MmallocatePageForMdl(__in PHYSICAL_ADDRESS LowAddress,__in PHYSICAL_ADDRESS HighAddress,__in PHYSICAL_ADDRESS SkipBytes,__in Size_T TotalBytes
);
76
PMDL
MDL三個介面函式 (2/3)
MmAllocatePagesForMdlEx(__in PHYSICAL_ADDRESS LowAddress,__in PHYSICAL_ADDRESS HighAddress__in Size_T TotalBytes,__in MEMORY_CACHING_TYPE CacheType,__in ULONG Flags
);
77
VOID
MDL三個介面函式 (3/3)
MmFreePagesFroMdl(__in PMDL MemoryDescriptorList
);
78
為在指定的位址範圍內獲得足夠的記憶體, MiAllocatePagesForMDl 函式搜尋零化、空閒和備用串列,盡可能滿足的記憶體需求。但函式回傳時,實際申請到的記憶體頁面數有可能少於客戶要求數量,所以必須檢查結果 MDL 物件中的成員值
MiAllocatePagesForMdl 並不將這些實體記憶體對應到虛擬位址空間,若需要虛擬位址,則須自己對應
MmFreePagesFromMdl 函式負責把實體記憶體歸還 PFN 資料庫
MDL
79
2012/6/13
洪國勛劉乃菀
4.5.4 修改頁面寫出器
80
當系統 memory消耗很多,零化序列、空閒序列和備用序列上的 page 數會減少,當少到一定程度時,修改頁面寫出器 (modified page writer)就會啟動,將已修改的頁面寫到外部記憶體中
Modified page writer主要做兩種工作:負責往「分頁檔」中寫頁面負責往「對應檔案」中寫頁面
一旦頁面內容被寫到外部記憶體,這些頁面就會被轉移到備用序列中,供系統或其他 Process 使用
Modified Page Writer (1/2)
81
當記憶體管理員初始化時, MmInitSystem 函式會建立一個系統緒程,其啟動程序為MiModifiedPageWriter , modified page writer 在即此實作
MiModifiedPageWriter 執行後會建立第二個緒程 MiMappedPageWriter
此兩個緒程都屬於即時優先順序,有較高優先權
Modified Page Writer (2/2)
82
MiModifiedPageWriter
初始化
啟動對應頁面寫出緒程
呼叫 MiModifiedPageWriterWorker函式
分頁檔的寫入操作 對應檔案頁面寫入操作
呼叫 MiGatherPagefilePages函式
呼叫 MiGatherMappedPages函式
等待事件訊號
事件觸發
無窮迴圈等待事件
83
使用 PsCreateSystemThread 函數創建一個新的SystemThread
啟動MiModifiedPageWriter
InitializeObjectAttributes (&ObjectAttributes, NULL, 0, NULL, NULL);if (!NT_SUCCESS(PsCreateSystemThread (
&ThreadHandle,THREAD_ALL_ACCESS,&ObjectAttributes, 0L,NULL,MiModifiedPageWriter, /* 啟動的函式 */NULL))) {
return FALSE;}
84
MiModifiedPageWriter初始化/* Initialize listheads as empty. */MmSystemShutdown = 0;KeInitializeEvent (&MmPagingFileHeader.Event, NotificationEvent, FALSE);KeInitializeEvent (&MmMappedFileHeader.Event, NotificationEvent, FALSE);
InitializeListHead(&MmPagingFileHeader.ListHead);InitializeListHead(&MmMappedFileHeader.ListHead);InitializeListHead(&MmFreePagingSpaceLow);
MmNumberOfMappedMdls = MmNumberOfPhysicalPages / (32 * 1024);
/* Allocate enough MDLs */if (MmNumberOfMappedMdls < 20) {
MmNumberOfMappedMdls = 20;}
85
啟動MiMappedPageWriter
InitializeObjectAttributes (&ObjectAttributes, NULL, 0, NULL, NULL);Status = PsCreateSystemThread (
&ThreadHandle,THREAD_ALL_ACCESS,&ObjectAttributes,0L,NULL,MiMappedPageWriter, /* 啟動的函式 */NULL);
ZwClose (ThreadHandle);MiModifiedPageWriterWorker (); /* 啟動MiModifiedPageWriterWorker */
86
MmModifiedPageWriterEvent觸發條件 (擇一 )
已修改過的 page 數超過預設上限可用 page 數少於 256 且修改串列中的 page 數達到預設上限
觸發後應獲得信號 (擇一 )寫入「分頁檔」寫入「對應檔」
MiMappedPagesTooOLdEvent每隔 300秒觸發一次防止對應檔案中的資料修改之後由於系統當機等意外事故造成大量資料未能及時刷新到外部記憶體
MiModifiedPageWriterWorker
87
MmModifiedPageWriterEvent
if (MmAvailablePages < MM_PLENTY_FREE_LIMIT) {/* If necessary, start the modified page writer. */if (MmModifiedPageListHead.Total >=
MmModifiedPageMaximum) {KeSetEvent (&MmModifiedPageWriterEvent, 0,
FALSE);}else if ((MmAvailablePages < MM_TIGHT_LIMIT) &&(MmModifiedPageListHead.Total >=
MmModifiedWriteClusterSize)) { KeSetEvent (&MmModifiedPageWriterEvent, 0, FALSE);
}}
可用 page數之預設上限
已修改過的page 數之預設上限
修改串列中的page 數之預設上限
88
分頁檔的寫入流程
掃描分頁檔點陣圖
檢查修改串列中的已修改頁面
其 PTE 設置於分頁檔中
設置 PNF 項目的寫入旗標位元
呼叫 IoAsynchronousPageWrite函式
start尋找連續空閒的 page
89
發出寫入操作
呼叫 IoAsynchronousPageWrite函式
/* Issue the write request. */Status = IoAsynchronousPageWrite (File,
&ModWriterEntry->Mdl,&StartingOffset,MiWriteComplete,(PVOID)ModWriterEntry,IrpPriority,&ModWriterEntry->u.IoStatus,&ModWriterEntry->Irp);
指定的完成函式
90
對應檔案的寫入流程
檢查 MmModifiedPageListHead 串列中的頁面
檢查與之相鄰的原型 PTE
將其插入到 MmMappedPageWriterList 中
設置事件 MmModifiedPageWriterEvent 有信號
通知對應頁面寫出器緒程
start
欲找出盡可能多的針對同一對應檔案的已修改頁面
91
2012/6/13
黃柏軒劉乃菀
4.5.5 行程 / 堆疊交換器
92
主函式 KeSwapProcessOrStack ,主要執行下列事項:換出 Kernel Stack <KiOutSwapKernelStacks>換出 Process <KiOutSwapProcesses>換入 Process <KiInSwapProcesses>換入 Kernel Stack <KiInSwapKernelStacks>
行程 / 堆疊交換器
93
記憶體管理員每隔 4 或 8 個他自身的檢查週期 (1秒鐘 ) ,就會設置事件的訊號
當進行一些有關緒程或行程的操作時,例如終止緒程、緒程脫離一個行程、就緒一個緒程等情形,若這些操作有必要換出或換入行程或核心堆疊,也會觸發事件
觸發條件
94
依序檢查 Waiting-In list上的 Thread ,若發現某個 Thread 已經等待超過 stack protect time 則將該 Thread 換出
執行前要先升高 IRQL(Interrupt Request Level)並 lock住資料庫
換出 Kernel Stack (1/6)
KiLockDispatcherDatabase(&OldIrql);
95
Define kernel stack protect time:若在小記憶體系統則設為 3秒其餘系統則設為 5x秒
換出 Kernel Stack (2/6)
#define SMALL_SYSTEM_STACK_PROTECT_TIME (3 * 75)#define LARGE_SYSTEM_STACK_PROTECT_TIME (SMALL_SYSTEM_STACK_PROTECT_TIME * 5)
:KiStackProtectTime =
LARGE_SYSTEM_STACK_PROTECT_TIME;
96
從等待串列中提取至多 5 個滿足條件的緒程,並將他們從等待串列中移除
換出 Kernel Stack (3/6)
#define MAXIMUM_THREAD_STACKS 5:
while ((NextEntry != &Prcb->WaitListHead) && (NumberOfThreads <
MAXIMUM_THREAD_STACKS)) {:
}
97
換出 Kernel Stack (4/6)
if ( WaitLimit < Thread->WaitTime ) {break;
} else if (Thread->Priority >= (LOW_REALTIME_PRIORITY +
9)) {RemoveEntryList(&Thread->WaitListEntry);
}else if (KiIsThreadNumericStateSaved(Thread)) {
ThreadObjects[NumberOfThreads] = Thread; NumberOfThreads += 1; RemoveEntryList(&Thread->WaitListEntry);
Process->StackCount -= 1;}
98
如果某 Process 的所有 Thread 都被換出,則該行程會被放到行程換出串列
Unlock 資料庫並降低 IRQL
換出 Kernel Stack (5/6)
if (Process->StackCount == 0)Process->State = ProcessOutTransition;
KiUnlockDispatcherDatabase(OldIrql);
99
Increment the last processor number.
執行換出 Kernel Stack
換出 Kernel Stack (6/6)
KiLastProcessor += 1;if (KiLastProcessor == (ULONG)KeNumberProcessors) {
KiLastProcessor = 0;}
while (NumberOfThreads > 0) { NumberOfThreads -= 1; Thread = ThreadObjects[NumberOfThreads]; MmOutPageKernelStack(Thread);
}
100
所謂換出行程,實際上就是依序換出四個頁面:工作集串列頁面VAD點陣圖頁面超空間分頁表頁面分頁目錄頁面
只有當一個 Process 的工作集已經降到最小,該行程才會在函式中被換出,否則,工作集管理員會修剪該Process 的工作集,直到被修剪為最小工作尺寸時,該行程才會再次被觸發換出操作
換出 Process (1/2)
101
依次檢查串列中的每一個 Process ,若該 Process的就緒緒程串列為空,則將之換出
若該 Process 的就緒緒程串列仍然為空,則將之移出記憶體
換出 Process (2/2)
if (NextEntry != &Process->ReadyListHead) {:
} else {Process->State = ProcessOutSwap;MmOutSwapProcess(Process);
}
Process->State = ProcessOutOfMemory;
102
對於已被換出的行程,只要他有一個緒程就緒,他就會被加入到串列中,並依次恢復四個頁面:分頁目錄頁面超空間分頁表頁面VAD點陣圖頁面工作集串列頁面
換入 Process
103
要換入 Kernel stack 的 Threads構成了一個串列KiStackInSwapListHead
當一個 Thread 變成就緒時,若是這個 Thread 的Kernel stack 不在記憶體中,則該 Thread就會被加入到串列中
函式會依次處理串列中的每一個 Thread ,執行實際的換入操作
換入 Kernel Stack
MmInPageKernelStack(Thread);
104
只能騰出很有限的實體記憶體,且必須要等到行程工作集中的其他頁面 ( 除上述所提到的四個頁面 ) 都被修剪過後才會發生
實際系統中, Process 的換出換入很少發生,一旦發生,常常意味著系統的記憶體不足以承擔目前的工作負載,而且系統的效能會急遽退化
Process 的換出與換入 (1/2)
105
交換器只是保證行程能夠執行,但無法解決效能瓶頸問題
解決方法增加實體記憶體減輕工作負載
Process 的換出與換入 (2/2)
106
2012/6/13
黃柏軒劉乃菀
4.5.6 低記憶體通知和高記憶體通知
107
當系統的可用記憶體很少或很充裕
Process 知道這些事件發生並做出反應
低記憶體通知和高記憶體通知
通知
108
MmInitSystem
MiInitializeMemoryEvents
Windows 的記憶體管理員的作法
呼叫
109
條件:可用記憶體低於 Threshold (MmLowMemoryThreshold)
MmLowMemoryThreshold 變數的值由系統實體記憶體的數量決定,除非應用系統在登錄中指定
低記憶體事件的觸發
110
RAM > 1GB第一個 1GB 為 32MB餘下每 1GB增加 8MB
128MB < RAM < 1GB第一個 128MB 為 3.1MB(800 pages)餘下每 128MB增加 4MB
RAM < 128MB等於 MmPlentyFreeThresholdRAM < 63MB = 400 pages ,否則 = 800 pages
MmLowMemoryThreshold
111
MmLowMemoryThreshold 的單位需由megabytes 轉換成 pages
MmLowMemoryThreshold
if (MmLowMemoryThreshold != 0) {MmLowMemoryThreshold *= ((1024 * 1024) /
PAGE_SIZE);}
112
RAM > 1GB
例如:當 RAM = 4GBMmLowMemoryThreshold = 32MB + ((0x100000 – 0x40000) >> 7) *
4K= 32MB + 3 * 8MB = (8K + 3 * 2K) pages
MmLowMemoryThreshold
if (MmNumberOfPhysicalPages > 0x40000) {MmLowMemoryThreshold = (32 * 1024 * 1024) /
PAGE_SIZE;MmLowMemoryThreshold+=((MmNumberOfPhysicalPages -
0x40000) >> 7);}
113
1GB > RAM > 128MB
例如:當 RAM = 512MBMmLowMemoryThreshold = 3.1MB + ((0x20000 – 0x8000) >> 5) * 4K= 3.1MB + 3 * 4MB = (800 + 3 * 1K) pages
MmLowMemoryThreshold
else if (MmNumberOfPhysicalPages > 0x8000) {MmLowMemoryThreshold += ((MmNumberOfPhysicalPages
- 0x8000) >> 5);}
114
If MmLowMemoryThreshold > 16K pagesThat is RAM > 5GB
MmLowMemoryThreshold
if (MmLowMemoryThreshold > (64 * 1024 * 1024) / PAGE_SIZE) {MmLowMemoryThreshold = (64 * 1024 * 1024) /
PAGE_SIZE;}
115
高記憶體事件 Threshold = 低記憶體事件Threshold 的 3倍
高記憶體事件的觸發
if (MmHighMemoryThreshold != 0) {MmHighMemoryThreshold *= ((1024 * 1024) / PAGE_SIZE);
} else {MmHighMemoryThreshold = 3 * MmLowMemoryThreshold;
}
if (MmHighMemoryThreshold < MmLowMemoryThreshold) {MmHighMemoryThreshold = MmLowMemoryThreshold;
}
116
2012/6/13
卓卿安吳政樺
4.6 工作集管理
117
工作集 ( Working Set) 是指一個行程目前正在使用的實體頁面的集合
Windows 中有三個工作集概念 行程工作集 系統工作集 工作階段工作集
工作集管理
4.6.1 Windows 工作集管理員
每個行程在建立位址空間時會對應一個專門的頁面存放它的工作集串列
其位址放於全域變數 MmWorkingSetList 中,型別為 MMWSL ,定義在下一頁
如何追蹤行程所用的實體頁面
typedef struct _MMWSL {
WSLE_NUMBER FirstFree; // 工作集空閒串列的起始 WSLE_NUMBER FirstDynamic; // 工作集中第一個可被修剪的頁面WSLE_NUMBER LastEntry // 工作集中最後一個可被修剪的頁面WSLE_NUMBER NextSlot; // 當要修剪頁面時,從此位置開始搜尋頁面
PMMWSLE Wsle;
WSLE_NUMBER LastInitializedWsle;
WSLE_NUMBER NonDirectCount;
PMMWSLE_HASH HashTable;
ULONG HashTableSize;
ULONG NumberOfCommittedPageTables;
PVOID HashTableStart;
PVOID HighestPermittedHashAddress;
ULONG NumberOfImageWaiters;
ULONG VadBitMapHint;
USHORT UsedPageTableEntries[MM_USER_PAGE_TABLE_PAGES];
ULONG CommittedPageTables[MM_USER_PAGE_TABLE_PAGES/(sizeof(ULONG)*8)]
} MMWSL, *PMMWSL;
MMWSL 型別定義
Wsle 是一個指標,實際上指向一個陣列,其陣列成員型別為 MMWSLE ,描述一個有效的頁面, MMWSLE 型別定義如下頁
工作集串列的組織結構
MMWSL 資料結構中, FirstDynamic 成員指向第一個可被修改的頁面,故在 MiTrimWorkingSet 函式中可看到,工作集修剪都從 FirstDynamic 開始
FirstFree 成員指向一個空閒串列開頭,這些項在內部形成一個單串列
LastEntry 成員指向最後一個使用項,因此MiTrimWorkingSet 在修剪工作集時絕對不會超過 LastEntry 項
typedef struct _MMWSLENTRY {
ULONG_PTR Valid : 1; // 頁面是否有效ULONG_PTR LockedInWs : 1;
ULONG_PTR LockedInMemory : 1;
ULONG_PTR Protection : 5;
ULONG_PTR Hashed : 1; // 頁面是否已被插入 Hash Table 中ULONG_PTR Direct : 1; // 頁面的 PFN 項目的 WsIndex 直接指向此項的索引ULONG_PTR Age : 2; // 頁面最近多久沒被訪問ULONG_PTR VirtualPageNumber : 20;
} MMWSLENTRY;
typedef struct _MMWSLE {
union {PVOID VirtualAddress;ULONG_PTR Long;MMWSLENTRY e1;
} u1;
} MMWSLE;
MMWSLE 型別定義
高 20 位元是頁面的虛擬位址低 12 位元是 MMWSLENTRY 中定義的旗標位元
工作集的最小值在行程建立時設定的, MmCreateProcessAddressSpace 函式的第一個參數
MmCreateProcessAddressSpace(
IN ULONG MinimumWorkingSetSize,
IN PEPROCESS NewProcess,
OUT PULONG_PTR DirectoryTableBase
);
在 4.3.1 節所述, WRK 中預設最小值是 50 ( 對於超過 32MB 記憶體的系統 ) ,最大值是 345 。
工作集的最小值和最大值
當行程使用很多頁面,並且系統確實有足夠的可用頁面時,行程工作集可以超越原本設定的最大值,但不能超過系統設定的一個全域最大值 MmMaximumWorkingSetSize ,是在系統初始化就設定好
MmMaximumWorkingSetSize = (WSLE_NUMBER)(MmAvailablePages - 512);
當系統記憶體吃緊時,而且行程不需用到很多頁面時,工作集可能降低到最小值以下
工作集的最小值和最大值
• 在 4.5 節中提到,記憶體管理員包含一個工作集管理員,會定期檢查各個行程的工作集,為了有效的使用實體記憶體,必要時須修剪占用記憶體較多的行程工作集。
• 在行程 EPROCESS 資料結構 中 VM 成員,其型別為 MMSUPPORT ,包含了工作集管理員在判斷是否需要修剪一個行程所需的各種資訊。
修剪工作集
typedef struct _MMSUPPORT {LIST_ENTRY WorkingSetExpansionLinks; LARGE_INTEGER LastTrimTime;
MMSUPPORT_FLAGS Flags;ULONG PageFaultCount;WSLE_NUMBER PeakWorkingSetSize;WSLE_NUMBER GrowthSinceLastEstimate;
WSLE_NUMBER MinimumWorkingSetSize; // 工作集最小值WSLE_NUMBER MaximumWorkingSetSize; // 工作集最小值struct _MMWSL *VmWorkingSetList; // 工作集虛擬地址指針WSLE_NUMBER Claim; //工作集老化情況評估值
WSLE_NUMBER NextEstimationSlot;WSLE_NUMBER NextAgingSlot;WSLE_NUMBER EstimatedAvailable;WSLE_NUMBER WorkingSetSize; // 工作集目前大小
EX_PUSH_LOCK WorkingSetMutex;} MMSUPPORT, *PMMSUPPORT;
MMSUPPORT 型別定義如果行程允許被修剪,其EPROCESS 的 Vm.WorkingSetExpansionLinks 會被加入到一個以全域變數 MmWorkingSetExpansionHead 為串列開頭的串列中
工作集結構
工作集管理員的執行流程圖
VOID KeBalanceSetManager (IN PVOID Context) {
switch (Status) {
.....
case WorkingSetManagerEvent:
MmWorkingSetManager();
break;
…..
}
}
VOID MmWorkingSetManager (VOID) {
.....
WorkingSetRequestFlags = MiComputeSystemTrimCriteria (&TrimCriteria);
if (WorkingSetRequestFlags != 0) {
MiProcessWorkingSets (WorkingSetRequestFlags, &TrimCriteria);
}
…..
}
工作集管理員的執行
呼叫MiComputerSystemTrimCriteria 函式以確定是否需要啟動修剪工作集或刷新工作集年齡
在記憶體管理員的平衡集管理員緒程中被呼叫,每隔 1 秒被觸發 1 次,或是當可用的記憶體降低至某一界限時觸發
ULONG MiComputeSystemTrimCriteria (IN PMMWS_TRIM_CRITERIA Criteria) {
.....
if ((Available <= PlentyFreePages) ||
(MiReplacing == TRUE) ||
(StandbyRemoved >= (Available >> 2))) {
MiRearrangeWorkingSetExpansionList ();
OutFlags = MI_TRIM_ALL_WORKING_SETS;
}
else if (Available < MM_ENORMOUS_LIMIT) {
OutFlags = MI_AGE_ALL_WORKING_SETS;
}
else {
OutFlags = 0;
}
.....
}
MmWorkingSetExpansionHead 串列中的工作集分成不同類別,並按照修剪優先順序重新排序EPROCESS 的 Vm->Claim 越大,放在修剪串列的前部,行程閒置時間越長放在串列前部。
工作集管理員的執行 (Cont.)
1. 目前系統可用頁面數Available少於當前需要的頁面數2. 目前工作集中,已經有被替換頁面的記錄3. 有超過系統可用頁面數四分之一的頁面被重新循環利用當作備用頁面
VOID KeBalanceSetManager (IN PVOID Context) {
switch (Status) {
.....
case WorkingSetManagerEvent:
MmWorkingSetManager();
break;
…..
}
}
VOID MmWorkingSetManager (VOID) {
.....
WorkingSetRequestFlags = MiComputeSystemTrimCriteria (&TrimCriteria);
if (WorkingSetRequestFlags != 0) {
MiProcessWorkingSets (WorkingSetRequestFlags, &TrimCriteria);
}
…..
}
工作集管理員的執行
呼叫 MiProcessWorkingSets 函式實際修剪或刷新工作集年齡
VOID MiProcessWorkingSets (IN ULONG WorkingSetRequestFlags, IN PMMWS_TRIM_CRITERIA TrimCriteria) {
…..
if (WorkingSetRequestFlags & MI_TRIM_ALL_WORKING_SETS) {
Trim = MiDetermineTrimAmount (TrimCriteria, VmSupport);
if ((Trim != 0) && ((TrimCriteria->TrimAllPasses > TrimCriteria->NumPasses) ||
(MmAvailablePages < TrimCriteria->DesiredFreeGoal))) {
Trim = MiTrimWorkingSet (Trim, VmSupport, TrimCriteria->TrimAge);
MiAgeWorkingSet (VmSupport, TrimCriteria->DoAging, NULL, &TrimCriteria->NewTotalClaim,
&TrimCriteria->NewTotalEstimatedAvailable);
}
}
else if (WorkingSetRequestFlags & MI_AGE_ALL_WORKING_SETS) {
MiAgeWorkingSet (VmSupport, TRUE, &WslesScanned, &TrimCriteria->NewTotalClaim,
&TrimCriteria->NewTotalEstimatedAvailable);
}
修剪工作集或刷新工作集年齡MiDetermineTrimAmount確定是否需要修剪此 Process 工作集以及修剪多老的頁面,並傳回修剪頁面數量MiTrimWorkingSet
執行修剪工作,在參數中指定:期望從工作集中修剪多少頁面、工作集資訊,以及修剪多老的頁面
MiAgeWorkingSet更新工作集中頁面的年齡值估計改行程工作集的 Vm->Claim 值
WSLE_NUMBER MiTrimWorkingSet (IN WSLE_NUMBER Reduction, IN PMMSUPPORT WsInfo, IN ULONG TrimAge){ ….. NumberLeftToRemove = Reduction; TryToFree = WorkingSetList->NextSlot; if (TryToFree > LastEntry || TryToFree < WorkingSetList->FirstDynamic) { TryToFree = WorkingSetList->FirstDynamic; } StartEntry = TryToFree; while (NumberLeftToRemove != 0) { …… WsleFlushList.FlushIndex[WsleFlushList.Count] = TryToFree; WsleFlushList.Count += 1; NumberLeftToRemove -= 1; …… TryToFree += 1; } …..}}
執行修剪工作
判斷搜尋的起始位置是否在有效頁面內
從 TryToFree開始迴圈搜索移除頁面
將其編號放 WsleFlushlist中
預計要刪除的頁面數 = 0時停止
WSLE_NUMBER MiTrimWorkingSet (IN WSLE_NUMBER Reduction, IN PMMSUPPORT WsInfo, IN ULONG TrimAge){ ….. if (WsleFlushList.Count != 0) { NumberNotFlushed = MiFreeWsleList (WsInfo, &WsleFlushList); NumberLeftToRemove += NumberNotFlushed; …… } …… if (WorkingSetList->FirstDynamic == WsInfo->WorkingSetSize) { MiRemoveWorkingSetPages (WsInfo); } else { if ((WsInfo->WorkingSetSize + 15 + (PAGE_SIZE / sizeof(MMWSLE))) < WorkingSetList->LastEntry) _ { if ((WsInfo->MaximumWorkingSetSize + 15 + (PAGE_SIZE / sizeof(MMWSLE))) < WorkingSetList->LastEntry ) { MiRemoveWorkingSetPages (WsInfo); } } }
釋放頁面
將要被釋放的頁面變成轉移狀態,轉移至備用串列或修改串列,並呼叫 MiRemoveWlse 函式將這些頁面移除
MiFreeWsleList 函式只是把被移除了頁面在陣列中做了無效的標記,所以如果 MiTrimWorkingSet 函式完成修剪工作後,發現工作集串列中存在許多無效的頁面,就會呼叫 MiRemoveWorkingSetPages 壓縮工作集串列,清除無效的資料
VOID MiAgeWorkingSet (IN PMMSUPPORT VmSupport, IN LOGICAL DoAging, IN PWSLE_NUMBER WslesScanned, IN OUT PPFN_NUMBER TotalClaim, IN OUT PPFN_NUMBER TotalEstimatedAvailable)
{
…..
PointerPte = MiGetPteAddress (Wsle[CurrentEntry].u1.VirtualAddress);
if (MI_GET_ACCESSED_IN_PTE(PointerPte) == 1) {
MI_RESET_WSLE_AGE(PointerPte, &Wsle[CurrentEntry]);
}
else {
MI_INC_WSLE_AGE(PointerPte, &Wsle[CurrentEntry]);
}
…...
}
更新工作集年齡
判斷頁面 PTE 的”存取過”位元是否已設定,如果被設定了,代表這個頁面從上次檢查以後已經被存取過,於是年齡值為 0 ,並清除該位
元
如果”存取過”位元未被設定,則代表上次檢查以來此頁面未被存取過,於是年齡值加 1 ,最大不超過
3
• MMWSL 使用雜湊表, 即 HashTable 成員,以加速 WSLE 的搜尋過程
由於 Wsle 和 HashTable 位於行程位址空間的固定位置處,空間管理方式直接以頁面對應做管理,而不是透過記憶體集區管理, HashTable 所佔據的頁面也算在工作集內
HashTable 成員
HashTable 成員typedef struct _MMWSL {
WSLE_NUMBER FirstFree; // 工作集空閒串列的起始 WSLE_NUMBER FirstDynamic; // 工作集中第一個可被修剪的頁面WSLE_NUMBER LastEntry // 工作集中最後一個可被修剪的頁面WSLE_NUMBER NextSlot; // 當要修剪頁面時,從此位置開始搜尋頁面
PMMWSLE Wsle;
WSLE_NUMBER LastInitializedWsle;
WSLE_NUMBER NonDirectCount;
PMMWSLE_HASH HashTable;
ULONG HashTableSize;
ULONG NumberOfCommittedPageTables;
PVOID HashTableStart;
PVOID HighestPermittedHashAddress;
ULONG NumberOfImageWaiters;
ULONG VadBitMapHint;
USHORT UsedPageTableEntries[MM_USER_PAGE_TABLE_PAGES];
ULONG CommittedPageTables[MM_USER_PAGE_TABLE_PAGES/(sizeof(ULONG)*8)]
} MMWSL, *PMMWSL;
每一項指定了一個從頁面虛擬位址到工作集串列陣列索引的關聯
HashTable 成員
• 以下是雜湊表成員項 MMWSLE_HASH 定義typedef struct _MMWSLE_HASH {
PVOID Key;WSLE_NUMBER Index;
} MMWSLE_HASH, *PMMWSLE_HASH;
以簡單的運算作為虛擬位置的對應並且用線性開放定址法 找到空閒的雜湊桶
HashTable 成員 (Cont.)
利用公式算出 Key ,然後在雜湊陣列中從 Key 這項開始尋找,直到找到一個空閒項,然後在這一項中插入該頁面所對應的雜湊項
線性開放定址法
• MiLocateWsle 函式根據參數中提示的索引項目,判斷是否此索引項目指向
此虛擬位址若是,則成功找到工作集串列若否,則利用參數中指定的虛擬位址在雜湊清單中搜尋
若找到,則傳回雜湊項目中的 Index若找不到,則在工作串列中進行線性搜尋, 直至找到為止,若還是找不到,則回傳失敗
以頁面的虛擬位址找到在工作集串列中的項
4.6.2 平衡集管理員
平衡集管理員 (balance set manager)系統初始化時建立,是一個系統緒程優先順序 16 ,最低的即時優先順序主要用途為維持系統記憶體資源的平衡
平衡集所有具備資源分配資格的行程的集合
平衡集管理員
概念當系統記憶體吃緊時
1. 從擁有較多記憶體的行程的工作集中換出一些頁面2. 把不滿足執行條件的行程排除在平衡集之外
實作基本上是工作集管理員的一個容器,為它提供一個執行環境 (每 1秒觸發 1次 )
每隔 4 或 8秒啟動行程 / 堆疊交換器
平衡集管理員
145
2012/6/13
吳彥諄吳政樺
4.7 記憶體監視工具 MemMon
146
MemMon 介紹 (1/3)
系統位置空間翰記憶體配置結構
目前系統中實體記憶體使用量和分頁檔頁面使用量
4.5.3節中提到的記憶體串列
147
MemMon 介紹 (2/3)
分頁使用量以及還有多少分頁
記憶體總量和可提供的記憶體量
分頁集和非分頁集記憶體量
148
MemMon 使用介紹 (3/3)
所選之行程記憶體配置結構
系統縮目前的行程樹
所選行程之虛擬記憶體資訊
所選行程的工作集資訊
149
MemMon 實作原理
MemMon
系統記憶體
行程記憶體
GetPerFromanceInfo
GlobalMemoryStatusEx
Module32First
Module32Next
Heap32First
Heap32Next
Thread32First
Thread32Next
CreateToolhelp32SnapshotProcess32First Process32Next
150
GlobalMemoryStatusEx (1/2)
GlobalMemoryStatusEx Struct MEMORYSTATUSEX
Header: winbase.h (Windows.h)
<<use>>
<<contant>><<contant>>
Minimum supported client-Windows XPMinimum supported server-Windows Server 2003
151
GlobalMemoryStatusEx (2/2)GlobalMemoryStatusEx
typedef struct _MEMORYSTATUSEX { DWORD dwLength; DWORD dwMemoryLoad; DWORDLONG ullTotalPhys; DWORDLONG ullAvailPhys; DWORDLONG ullTotalPageFile; DWORDLONG ullAvailPageFile; DWORDLONG ullTotalVirtual; DWORDLONG ullAvailVirtual; DWORDLONG ullAvailExtendedVirtual;
} MEMORYSTATUSEX, *LPMEMORYSTATUSEX;
所有的實體記憶體
可用的實體記憶體
所有的 Page File
可用的 Page File
所有的虛擬記憶體可用的虛擬記憶體
擴展的虛擬記憶體
使用記憶體的百分比
152
153
GetPerformanceInfo (1/2)
GetPerformanceInfo Struct PERFORMANCE_INFORMATION
Header: Psapi.h
<<use>>
<<contant>><<contant>>
Minimum supported client-Windows XPMinimum supported server-Windows Server 2003
154
GetPerformanceInfo (2/2)GetPerformanceInfo
typedef struct _PERFORMANCE_INFORMATION { DWORD cb; SIZE_T CommitTotal; SIZE_T CommitLimit; SIZE_T CommitPeak; SIZE_T PhysicalTotal; SIZE_T PhysicalAvailable; SIZE_T SystemCache; SIZE_T KernelTotal; SIZE_T KernelPaged; SIZE_T KernelNonpaged; SIZE_T PageSize; DWORD HandleCount; DWORD ProcessCount; DWORD ThreadCount;
} PERFORMANCE_INFORMATION, *PPERFORMANCE_INFORMATION;
系統可 commit 的最大 page 數量
系統有多少實體的記憶體以 page計算系統有多少可使用的記憶體以 page計算系統有多少快取記憶體
The sum of the memory currently in the paged and nonpaged kernel pools, in pages.
Page大小 , 以 byte計算Process 數量
系統當下 commit多少 page
執行緒述量
155
系統記憶體
MemMon
系統記憶體
GetPerFromanceInfo
GlobalMemoryStatusEx
由上述兩個 api 以及 MemMom每秒掃描一次,可以知道總觀性的記憶體使用狀況,以及藉由windows.h 、 psapi.h 所提供的資料結構,可了解更詳細的數值,實作出系統記憶體介面。
156
MemMon 行程記憶體
Module32First
Module32Next
Heap32First
Heap32Next
Thread32First
Thread32Next
CreateToolhelp32Snapshot
Process32First Process32Next
157
CreateToolhelp32Snapshot
CreateToolhelp32Snapshot
dwFlags
Header: TlHelp32.h
<<in>><<contant>>
Minimum supported client-Windows XPMinimum supported server-Windows Server 2003
th32ProcessID<<in>>
擷取系統目前部分的狀態
必須為再擷取出的process之一,並且視為此觀察區快中的第一個 process
th32ProcessID
<<out>> 成功則回傳一個open handle失敗回傳 INVALID_HANDLE_VALUE
158
Overview
Module32First
Module32Next
Heap32First
Heap32Next
Thread32First
Thread32Next
Process32First
Process32Next
CreateToolhelp32Snapshot<<use once>>
struct MODULEENTRY32<<use>>
struct HEAPENTRY32<<use>>
struct THREADENTRY32<<use>>
struct PROCESSNTRY32<<use>>
Header: TlHelp32.h
<<contant>>
Minimum supported client
-Windows XPMinimum supported server
-Windows Server 2003
159
HEAPENTRY32(2/2)Heap32Next & Heap32First
typedef struct tagHEAPENTRY32 { SIZE_T dwSize; HANDLE hHandle; ULONG_PTR dwAddress; SIZE_T dwBlockSize; DWORD dwFlags; DWORD dwLockCount; DWORD dwResvd; DWORD th32ProcessID; ULONG_PTR th32HeapID;
} HEAPENTRY32, *PHEAPENTRY32;
指向線性位置的開頭
Process ID
Heap ID
指向 heap block
160
THREADENTRY32(2/2)Thread32Next & Thread32First
typedef struct tagTHREADENTRY32 { DWORD dwSize; DWORD cntUsage;
DWORD th32ThreadID; DWORD th32OwnerProcessID; LONG tpBasePri; LONG tpDeltaPri; DWORD dwFlags;
} THREADENTRY32, *PTHREADENTRY32;
屬於哪個 process ID擁有此Thread
此 Thread 的優先權
Thread ID
161
MODULEENTRY32Module32Next & Module32First
typedef struct tagMODULEENTRY32 { DWORD dwSize; DWORD th32ModuleID; DWORD th32ProcessID; DWORD GlblcntUsage; DWORD ProccntUsage; BYTE *modBaseAddr; DWORD modBaseSize; HMODULE hModule; TCHAR szModule[MAX_MODULE_NAME32 + 1]; TCHAR szExePath[MAX_PATH]; } MODULEENTRY32, *PMODULEENTRY32;
Process ID
此 module 的起始位置
此 module 有多大
此 module 的名字
此 module 的 path
Module ID
162
PROCESSENTRY32Process32Next & Process32First
typedef struct tagPROCESSENTRY32 { DWORD dwSize; DWORD cntUsage; DWORD th32ProcessID; ULONG_PTR th32DefaultHeapID; DWORD th32ModuleID; DWORD cntThreads; DWORD th32ParentProcessID; LONG pcPriClassBase; DWORD dwFlags; TCHAR szExeFile[MAX_PATH];
} PROCESSENTRY32, *PPROCESSENTRY32;
Process ID
Module id
此 process 的 parent id
此 process 的名字
此 process擁有幾個執行序
163
Process id
Module Name
執行緒數量
此 Module 的 path
164
行程記憶體利用上述的 API ,就可以輕鬆的偵測到目前系統記憶體狀態,在根據每過1秒就掃描一次,就可以做到偵測行程記憶體的資訊。
這樣就可以做到這些介面大部分的資訊了!
165
2012/6/13
吳彥諄吳政樺
4.8 本章總結
166
這章讓我們學到:Windows 記憶體管理員的基礎知識
Ex. 如何管理分頁及非分頁記憶體Ex. 如何管理實體記憶體
記憶體管理員如何有效低管理有限資源並降低管理負擔Windows 記憶體管理員的最佳化設施MemMon 的一些實作原理
本章總結