Windows內存管理分析(一)

筆記參考毛德操先生所著《Windows內核情景分析》,使用代碼為ReactOS 0.4.7,相比原著更新了一些數據結構幫助理解新的數據結構。

本文基於IA-32架構,假定讀者已經了解IA-32架構下的MMU(具體請閱讀Intel 手冊)

如何工作以及擁有良好的數據結構基礎

一、虛擬內存的管理

進程地址空間的信息 由MMSUPPORT結構體所描述,每個EProcess結構體都會有一個結構為MMSUPPORT的Va成員,描述著這個進程的整個虛擬內存的信息,但是它並不管理這些信息。結構體成員如下

成員WorkingSetExpansionLinks把所有的MMSUPPORT串聯在一起,鏈表頭位於全局變數MmWorkingSetExpansionHead。PeakWorkingSetSize為工作集的峰值,PageFaultCount頁面錯誤次數。

而進程一個結構為MM_AVL_TABLE的VadRoot結果管理著所有虛擬內存,進行管理的成員主要是BalancedRoot,是平衡樹的根節點

NumberGenericTableElements成員描述了這個結構具體有多少個元素,NodeHint是命中的結點指針,NodeFreeHint是釋放的時候指向的結點,都利於編程。

一整塊地址空間,通常應該有許多的區域,這些區域由MEMORY_AREA描述,結構體如下

成員VadNode是MMVAD 虛擬地址描述符,type是AREA的類型,DeleteInProgress是代表被刪除。Sement和ViewOffset將在共享內存區進行詳細描述。MMVAD結構體如下

從虛擬地址描述符中可以看出虛擬描述符是一個二叉樹,實際上是一顆平衡樹,所以MEMORY_AREA這個結構體也是一個平衡二叉樹結構,虛擬地址描述符中還描述了這個起始的虛擬頁號和終止頁號(如果只有一頁那麼這兩個值相同)。

此時讀者可能有問題,MM_AVL_TABLE表中BalancedRoot的類型為MMADDRESS_NODE,而AREA的結構體卻和此不同,如何將AREA存入AVL_TABLE表中呢。實際上MMADDRESS_NODE結構體的定義如下

仔細觀察後發現MMADDRESS_NODE正是VAD的一部分,所以包含VAD描述符的結構都能存進這個AVL_TABLE表中。

MEMORY_AREA有一個RegionListHead成員,定義為

即每一個MEMORY_AREA會管理著一系列的REGION,為什麼這樣設計呢,是因為一個AREA裡面的地址可能有很多不同的屬性,但是如果是一個REGION,裡面的虛擬地址就會保持同一個頁面屬性。

這裡介紹一下進程工作集的數據結構,我們知道進程結構體EPROCESS有一個類型為MMSUPPORT的Vm成員描述著虛擬內存,它的一個類型為PMMWSL 的VmWorkingSetList描述了工作集

FirstFree是第一個空閑項,FirstDynamic是第一個可以修剪的頁面,LastEntry是最後一項,wsle是一個數組,每個數組的元素描述著一個有效的頁面,UsedPageTableEntries數組成員,元素代表著這個頁面被引用了多少次,這是一個數組,為什麼是768個呢,我們假設一個頁面是4K,乘以768就是3GB。我們知道一個進程可以是2GB,但是通過設置可以最多支持3GB,所以這也是一開始就設計好的內容。

接下來看一些函數實現來加深理解

MmLocateMemoryAreaByAddress函數參數是一個結構體MMSUPPORT地址空間的描述,第二個參數是一個地址,功能是返回第二個參數給定地址的AREA指針

首先是獲取這個地址空間所屬的進程,由於第一個參數賦值的時候常常是進程的Va成員,或者是內核的MiRosKernelVadRoot成員,所以如果是進程的話就可以通過Va成員在結構體的偏移來定位到EProcess得到進程結構體。這時候如果獲取到了EProcess就判定是進程,如果為空,就代表是內核的請求(內核的虛擬內存空間MMSUPPORT是一個全局變數MiRosKernelVadRoot,通過這個全局變數來管理內核所有的虛擬空間)

接下來分析MiCheckForConflictingNode

這個函數參數分別是起始虛擬頁號,終止頁號,MM_AVL_TABLE地址,輸出為MMADDRESS_NODE節點,簡單來說就是給定一個範圍的虛擬地址(起始虛擬頁號和終止虛擬頁號給定)在MM_AVL_TABLE中找出有這個地址的節點,那麼就有三種情況了,一種是完全重合,一種是部分重合,最後就是整個區域中都沒有這個地址。我們來看看是代碼是怎麼處理這種情況的。首先是通過搜索二叉樹的演算法開始計算,我們看一下這段代碼

Else成立的條件是當這個起始虛擬頁號和終止虛擬頁號完全被包含在這個節點中就會返回。

如果不是完全包含或者是完全不存在,已經是遍歷完畢時

如果說起始頁號地址比這個節點的終止虛擬頁號大,應該插入在NodeOrParent的右邊,否則就是應該插入在左邊。

返回到剛才分析的函數,如果調用了此函數返回了TableInsertAsRight、TableInsertAsLeft或者TableEmptyTree的話,就會返回空,代表當前並沒有此地址。如果有的話,就返回這個節點

分析完了AREA尋找函數再看AREA插入函數

參數為一個地址空間MMSUPPORT和一個要插入的AREA,首先和查找一樣,判斷是內核還是進程,如果是進程就插入到進程的VadRoot,如果是內核就插入到內核的MiRosKernelVadRoot,沒什麼好分析的了,剩下的都是數據結構的內容。刪除函數

MiRemoveNode同理。

MmFindGap會從VadRoot或者MiRosKernelVadRoot中遍歷出一個參數能夠支持的間隙並且粒度對齊大小的一塊虛擬地址空間。具體的查找方法也是數據結構的內容,不予贅述。

接下來我們來分析一個由AREA進行管理的REGION鏈表,我們知道,REGION內所有地址安全屬性一致,如果此時想要更改REGION內部部分的安全屬性就必須進行切割。MmSplitRegion負責進行這樣的工作

這段代碼參數要求為InitialRegion是給定要改變的REGION,InitialBaseAddress是REGION的基地址(因為REGION結構中並沒有保存基地址的成員),要改變屬性的起始地址StartAddress,改變的長度Length,新REGION的類型NewType,新保護屬性NewProtect,MMSUPPORT結構和AlterFunc函數指針,AlterFunc函數是為了讓實際地址安全屬性發生真的改變,是體系相關的內容,改變了REGION屬性後就會回調這個函數對體系的內存屬性進行修改。分離出2個REGION後這2個再進行連接。MmFindRegion函數對這個鏈表進行參數RegionListHead(一般是AREA的RegionListHead設定)遍歷,具體的代碼就不用說了,數據結構相關知識。

學習完AREA和REGION我們來看一下MmCreateMemoryArea函數,參數依次是PMMSUPPORT AddressSpace、ULONG Type,PVOID *BaseAddress, ULONG_PTR Length, ULONG Protect, PMEMORY_AREA *Result, ULONG AllocationFlags, ULONG Granularity),意思為在一個地址空間中申請一個BaseAddress為基址的,大小為Length,保護屬性為Protect的一個AREA,結構保存在Result中

分段來進行閱讀

如果參數表明需要靜態內存,就可以到系統預先分配好的MiStaticMemoryAreas結構中直接獲取,優點就是速度快,已經提前安排好,缺點就是容量有限。MiStaticMemoryAreaCount記錄了下一次可用下標。

初始化後,如果沒有給定基址,那麼就用MmFindGap查找到一個間隙,找到後就設置根據這個間隙來設置AREA的起始Vpn和終止Vpn;

如果給定了基址就要判斷之前有沒有AREA已經在此,如果有的話就返回錯誤。沒有的話就同樣設置AREA的起始VPN和終止VPN,然後插入到地址空間之中。MmInsertMemoryArea已經分析過,不予贅述。

至此,我們已經看到了一個大概的虛擬內存管理的模型,首先是進程結構體的Va描述著他們自己的內存空間,由進程的VadRoot或者MiRosKernelVadRoot(內核)進行管理,VadRoot是一個平衡二叉樹,裡面存放著由AREA構成的節點,而每個AREA又管理著一個REGION鏈表,REGION裡面的地址空間保證是一個屬性,AREA則無此要求。

當觀察源代碼的時候,我們可以看到申請虛擬內存時並沒有映射物理內存的代碼,實際上就是如此,進程的VadRoot管理的一系列虛擬內存中不一定映射到物理內存,只有在使用的時候才會映射。

本文由看雪論壇 黑色書卷 原創 轉載請註明來自看雪社區

關注看雪學院公眾號:ikanxue

更多乾貨等著你~

weixin.qq.com/r/M3W7oxb (二維碼自動識別)

推薦閱讀:

TAG:MicrosoftWindows | 內存管理 | 英特爾Intel |