內核是如何管理內存的?

原文:http://blog.csdn.net/drshenlei/article/details/4350928


 

原文標題:How The Kernel Manages Your Memory

原文地址:http://duartes.org/gustavo/blog/

 

[注:本人水平有限,只好挑一些國外高手的精彩文章翻譯一下。一來自己複習,二來與大家分享。]

 

    在仔細審視了進程的虛擬地址佈局之後,讓我們把目光轉向內核以及其管理用戶內存的機制。再次從gonzo圖示開始:

 

 

 

    Linux進程在內核中是由task_struct的實例來表示的,即進程描述符。task_struct的mm字段指向內存描述符(memory descriptor),即mm_struct,一個程序的內存的執行期摘要。它存儲了上圖所示的內存段的起止位置,進程所使用的物理內存頁的數量rss表示Resident Set Size),虛擬內存空間的使用量,以及其他信息。我們還可以在內存描述符中找到用於管理程序內存的兩個重要結構:虛擬內存區域集合(the set of virtual memory areas)及頁表(page table)。Gonzo的內存區域如下圖所示:

 

 

    每一個虛擬內存區域(簡稱VMA)是一個連續的虛擬地址範圍;這些區域不會交疊。一個vm_area_struct的實例完備的描述了一個內存區域,包括它的起止地址,決定訪問權限和行爲的標誌位,還有vm_file字段,用於指出被映射的文件(如果有的話)。一個VMA如果沒有映射到文件,則是匿名的(anonymous)。除memory mapping 段以外,上圖中的每一個內存段(如:堆,棧)都對應於一個單獨的VMA。這並不是強制要求,但在x86機器上經常如此。VMA並不關心它在哪一個段。

 

    一個程序的VMA同時以兩種形式存儲在它的內存描述符中:一個是按起始虛擬地址排列的鏈表,保存在mmap字段;另一個是紅黑樹,根節點保存在mm_rb字段。紅黑樹使得內核可以快速的查找出給定虛擬地址所屬的內存區域。當你讀取文件/proc/pid_of_process/maps時,內核只須簡單的遍歷指定進程的VMA鏈表,並打印出每一項來即可。

 

    在Windows中,EPROCESS塊可以粗略的看成是task_struct和mm_struct的組合。VMA在Windows中的對應物時虛擬地址描述符(Virtual Address Descriptor),或簡稱VAD;它們保存在平衡樹中(AVL tree)。你知道Windows和Linux最有趣的地方是什麼嗎?就是這些細小的不同點。

 

        4GB虛擬地址空間被分割爲許多(page)。x86處理器在32位模式下所支持的頁面大小爲4KB,2MB和4MB。Linux和Windows都使用4KB大小的頁面來映射用戶部分的虛擬地址空間。第0-4095字節在第0頁,第4096-8191字節在第1頁,以此類推。VMA的大小必須是頁面大小的整數倍。下圖是以4KB分頁的3GB用戶空間:

 

 

    處理器會依照頁表(page table)來將虛擬地址轉換到物理內存地址。每個進程都有屬於自己的一套頁表;一旦進程發生了切換,用戶空間的頁表也會隨之切換。Linux在內存描述符的pgd字段保存了一個指向進程頁表的指針。每一個虛擬內存頁在頁表中都有一個與之對應的頁表項(page table entry),簡稱PTE。它在普通的x86分頁機制下,是一個簡單的4字節記錄,如下圖所示:

 

        Linux有一些函數可以用於讀取設置PTE中的每一個標誌。P位告訴處理器虛擬頁面是否存在於(present)物理內存中。如果是0,訪問這個頁將觸發頁故障(page fault)。記住,當這個位是0時,內核可以根據喜好,隨意的使用其餘的字段。R/W標誌表示讀/寫;如果是0,頁面就是隻讀的。U/S標誌表示用戶/管理員;如果是0,則這個頁面只能被內核訪問。這些標誌用於實現只讀內存和保護內核空間。

 

        D位和A位表示數據髒(dirty)和訪問過(accessed)。髒表示頁面被執行過寫操作,訪問過表示頁面被讀或被寫過。這兩個標誌都是粘滯的:處理器只會將它們置位,之後必須由內核來清除。最後,PTE還保存了對應該頁的起始物理內存地址,對齊於4KB邊界。PTE中的其他字段我們改日再談,比如物理地址擴展(Physical Address Extension)。

 

    虛擬頁面是內存保護的最小單元,因爲頁內的所有字節都共享U/S和R/W標誌。然而,同樣的物理內存可以被映射到不同的頁面,甚至可以擁有不同的保護標誌。值得注意的是,在PTE中沒有對執行許可(execute permission)的設定。這就是爲什麼經典的x86分頁可以執行位於stack上的代碼,從而爲黑客利用堆棧溢出提供了便利(使用return-to-libc和其他技術,甚至可以利用不可執行的堆棧)。PTE缺少不可執行(no-execute)標誌引出了一個影響更廣泛的事實:VMA中的各種許可標誌可能會也可能不會被明確的轉換爲硬件保護。對此,內核可以盡力而爲,但始終受到架構的限制。

 

    虛擬內存並不存儲任何東西,它只是將程序地址空間映射到底層的物理內存上,後者被處理器視爲一整塊來訪問,稱作物理地址空間(physical address space)。對物理內存的操作還與總線有點聯繫,好在我們可以暫且忽略這些並假設物理地址範圍以字節爲單位遞增,從0到最大可用內存數。這個物理地址空間被內核分割爲一個個頁幀(page frame)。處理器並不知道也不關心這些幀,然而它們對內核至關重要,因爲頁幀是物理內存管理的最小單元。Linux和Windows在32位模式下,都使用4KB大小的頁幀;以一個擁有2GB RAM的機器爲例:

 

 

 

    在Linux中,每一個頁幀都由一個描述符一些標誌所跟蹤。這些描述符合在一起,記錄了計算機內的全部物理內存;可以隨時知道每一個頁幀的準確狀態。物理內存是用buddy memory allocation技術來管理的,因此如果一個頁幀可被buddy 系統分配,則它就是可用的(free)。一個被分配了的頁幀可能是匿名的(anonymous),保存着程序數據;也可能是頁緩衝的(page cache),保存着一個文件或塊設備的數據。還有其他一些古怪的頁幀使用形式,但現在先不必考慮它們。Windows使用一個類似的頁幀編號(Page Frame Number簡稱PFN)數據庫來跟蹤物理內存。

 

    讓我們把虛擬地址區域,頁表項,頁幀放到一起,看看它們到底是怎麼工作的。下圖是一個用戶堆的例子:

 

 

 

    藍色矩形表示VMA範圍內的頁,箭頭表示頁表項將頁映射到頁幀上。一些虛擬頁並沒有箭頭;這意味着它們對應的PTE的存在位(Present flag)爲0。形成這種情況的原因可能是這些頁還沒有被訪問過,或者它們的內容被系統換出了(swap out)。無論那種情況,對這些頁的訪問都會導致頁故障(page fault),即使它們處在VMA之內。VMA和頁表的不一致看起來令人奇怪,但實際經常如此。

 

    一個VMA就像是你的程序和內核之間的契約。你請求去做一些事情(如:內存分配,文件映射等),內核說“行”,並創建或更新適當的VMA。但它並非立刻就去完成請求,而是一直等到出現了頁故障纔會真正去做。內核就是一個懶惰,騙人的敗類;這是虛擬內存管理的基本原則。它對大多數情況都適用,有些比較熟悉,有些令人驚訝,但這個規則就是這樣:VMA記錄了雙方商定做什麼,而PTE反映出懶惰的內核實際做了什麼。這兩個數據結構共同管理程序的內存;都扮演着解決頁故障,釋放內存,換出內存(swapping memory out)等等角色。讓我們看一個簡單的內存分配的例子:

 

 

 

    當程序通過brk()系統調用請求更多的內存時,內核只是簡單的更新堆的VMA,然後說搞好啦。其實此時並沒有頁幀被分配,新的頁也並沒有出現於物理內存中。一旦程序試圖訪問這些頁,處理器就會報告頁故障,並調用do_page_fault()。它會通過調用find_vma()搜索哪一個VMA含蓋了產生故障的虛擬地址。如果找到了,還會根據VMA上的訪問許可來比對檢查訪問請求(讀或寫)。如果沒有合適的VMA,也就是說內存訪問請求沒有與之對應的合同,進程就會被處以段錯誤(Segmentation Fault)的罰單。

 

    當一個VMA被找到後,內核必須處理這個故障,方式是察看PTE的內容以及VMA的類型。在我們的例子中,PTE顯示了該頁並不存在。事實上,我們的PTE是完全空白的(全爲0),在Linux中意味着虛擬頁還沒有被映射。既然這是一個匿名的VMA,我們面對的就是一個純粹的RAM事務,必須由do_anonymous_page()處理,它會分配一個頁幀並生成一個PTE,將出故障的虛擬頁映射到那個剛剛分配的頁幀上。

 

    事情還可能有些不同。被換出的頁所對應的PTE,例如,它的Present標誌是0但並不是空白的。相反,它記錄了頁面內容在交換系統中的位置,這些內容必須從磁盤讀取出來並通過do_swap_page()加載到一個頁幀當中,這就是所謂的major fault

 

    至此我們走完了“內核的用戶內存管理”之旅的前半程。在下一篇文章中,我們將把文件的概念也混進來,從而建立一個內存基礎知識的完成畫面,並瞭解其對系統性能的影響。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章