Linux内核如何管理内存
在學習了進程的?虛擬地址布局?之后,讓我們回到內核,來學習它管理用戶內存的機制。這里再次使用 Gonzo:
?
Linux kernel mm_struct
Linux 進程在內核中是作為進程描述符?task_struct?(LCTT 譯注:它是在 Linux 中描述進程完整信息的一種數據結構)的實例來實現的。在 task_struct 中的?mm?域指向到內存描述符,mm_struct?是一個程序在內存中的執行摘要。如上圖所示,它保存了起始和結束內存段,進程使用的物理內存頁面的?數量(RSS 常駐內存大小Resident Set Size?)、虛擬地址空間使用的?總數量、以及其它片斷。 在內存描述符中,我們可以獲悉它有兩種管理內存的方式:虛擬內存區域集和頁面表。Gonzo 的內存區域如下所示:
?
Kernel memory descriptor and memory areas
每個虛擬內存區域(VMA)是一個連續的虛擬地址范圍;這些區域絕對不會重疊。一個?vm_area_struct?的實例完整地描述了一個內存區域,包括它的起始和結束地址,flags?決定了訪問權限和行為,并且?vm_file?域指定了映射到這個區域的文件(如果有的話)。(除了內存映射段的例外情況之外,)一個 VMA 是不能匿名映射文件的。上面的每個內存段(比如,堆、棧)都對應一個單個的 VMA。雖然它通常都使用在 x86 的機器上,但它并不是必需的。VMA 也不關心它們在哪個段中。
一個程序的 VMA 在內存描述符中是作為?mmap?域的一個鏈接列表保存的,以起始虛擬地址為序進行排列,并且在?mm_rb?域中作為一個?紅黑樹?的根。紅黑樹允許內核通過給定的虛擬地址去快速搜索內存區域。在你讀取文件?/proc/pid_of_process/maps?時,內核只是簡單地讀取每個進程的 VMA 的鏈接列表并顯示它們。
在 Windows 中,EPROCESS?塊大致類似于一個 task_struct 和 mm_struct 的結合。在 Windows 中模擬一個 VMA 的是虛擬地址描述符,或稱為?VAD;它保存在一個?AVL 樹?中。你知道關于 Windows 和 Linux 之間最有趣的事情是什么嗎?其實它們只有一點小差別。
4GB 虛擬地址空間被分配到頁面中。在 32 位模式中的 x86 處理器中支持 4KB、2MB、以及 4MB 大小的頁面。Linux 和 Windows 都使用大小為 4KB 的頁面去映射用戶的一部分虛擬地址空間。字節 0-4095 在頁面 0 中,字節 4096-8191 在頁面 1 中,依次類推。VMA 的大小?必須是頁面大小的倍數?。下圖是使用 4KB 大小頁面的總數量為 3GB 的用戶空間:
?
4KB Pages Virtual User Space
處理器通過查看頁面表去轉換一個虛擬內存地址到一個真實的物理內存地址。每個進程都有它自己的一組頁面表;每當發生進程切換時,用戶空間的頁面表也同時切換。Linux 在內存描述符的?pgd?域中保存了一個指向進程的頁面表的指針。對于每個虛擬頁面,頁面表中都有一個相應的頁面表條目(PTE),在常規的 x86 頁面表中,它是一個簡單的如下所示的大小為 4 字節的記錄:
?
x86 Page Table Entry (PTE) for 4KB page
Linux 通過函數去?讀取?和?設置?PTE 條目中的每個標志位。標志位 P 告訴處理器這個虛擬頁面是否在物理內存中。如果該位被清除(設置為 0),訪問這個頁面將觸發一個頁面故障。請記住,當這個標志位為 0 時,內核可以在剩余的域上做任何想做的事。R/W 標志位是讀/寫標志;如果被清除,這個頁面將變成只讀的。U/S 標志位表示用戶/超級用戶;如果被清除,這個頁面將僅被內核訪問。這些標志都是用于實現我們在前面看到的只讀內存和內核空間保護。
標志位 D 和 A 用于標識頁面是否是“臟的”或者是已被訪問過。一個臟頁面表示已經被寫入,而一個被訪問過的頁面則表示有一個寫入或者讀取發生過。這兩個標志位都是粘滯位:處理器只能設置它們,而清除則是由內核來完成的。最終,PTE 保存了這個頁面相應的起始物理地址,它們按 4KB 進行整齊排列。這個看起來不起眼的域是一些痛苦的根源,因為它限制了物理內存最大為?4 GB。其它的 PTE 域留到下次再講,因為它是涉及了物理地址擴展的知識。
由于在一個虛擬頁面上的所有字節都共享一個 U/S 和 R/W 標志位,所以內存保護的最小單元是一個虛擬頁面。但是,同一個物理內存可能被映射到不同的虛擬頁面,這樣就有可能會出現相同的物理內存出現不同的保護標志位的情況。請注意,在 PTE 中是看不到運行權限的。這就是為什么經典的 x86 頁面上允許代碼在棧上被執行的原因,這樣會很容易導致挖掘出棧緩沖溢出漏洞(可能會通過使用?return-to-libc?和其它技術來找出非可執行棧)。由于 PTE 缺少禁止運行標志位說明了一個更廣泛的事實:在 VMA 中的權限標志位有可能或可能不完全轉換為硬件保護。內核只能做它能做到的,但是,最終的架構限制了它能做的事情。
虛擬內存不保存任何東西,它只是簡單地?映射?一個程序的地址空間到底層的物理內存上。物理內存被當作一個稱之為物理地址空間的巨大塊而由處理器訪問。雖然內存的操作涉及到某些總線,我們在這里先忽略它,并假設物理地址范圍從 0 到可用的最大值按字節遞增。物理地址空間被內核進一步分解為頁面幀。處理器并不會關心幀的具體情況,這一點對內核也是至關重要的,因為,頁面幀是物理內存管理的最小單元。Linux 和 Windows 在 32 位模式下都使用 4KB 大小的頁面幀;下圖是一個有 2 GB 內存的機器的例子:
?
Physical Address Space
在 Linux 上每個頁面幀是被一個?描述符?和?幾個標志?來跟蹤的。通過這些描述符和標志,實現了對機器上整個物理內存的跟蹤;每個頁面幀的具體狀態是公開的。物理內存是通過使用?Buddy 內存分配(LCTT 譯注:一種內存分配算法)技術來管理的,因此,如果一個頁面幀可以通過 Buddy 系統分配,那么它是未分配的(free)。一個被分配的頁面幀可以是匿名的、持有程序數據的、或者它可能處于頁面緩存中、持有數據保存在一個文件或者塊設備中。還有其它的異形頁面幀,但是這些異形頁面幀現在已經不怎么使用了。Windows 有一個類似的頁面幀號(Page Frame Number (PFN))數據庫去跟蹤物理內存。
我們把虛擬內存區域(VMA)、頁面表條目(PTE),以及頁面幀放在一起來理解它們是如何工作的。下面是一個用戶堆的示例:
?
Physical Address Space
藍色的矩形框表示在 VMA 范圍內的頁面,而箭頭表示頁面表條目映射頁面到頁面幀。一些缺少箭頭的虛擬頁面,表示它們對應的 PTE 的當前標志位被清除(置為 0)。這可能是因為這個頁面從來沒有被使用過,或者是它的內容已經被交換出去了。在這兩種情況下,即便這些頁面在 VMA 中,訪問它們也將導致產生一個頁面故障。對于這種 VMA 和頁面表的不一致的情況,看上去似乎很奇怪,但是這種情況卻經常發生。
一個 VMA 像一個在你的程序和內核之間的合約。你請求它做一些事情(分配內存、文件映射、等等),內核會回應“收到”,然后去創建或者更新相應的 VMA。 但是,它?并不立刻?去“兌現”對你的承諾,而是它會等待到發生一個頁面故障時才去?真正?做這個工作。內核是個“懶惰的家伙”、“不誠實的人渣”;這就是虛擬內存的基本原理。它適用于大多數的情況,有一些類似情況和有一些意外的情況,但是,它是規則是,VMA 記錄?約定的?內容,而 PTE 才反映這個“懶惰的內核”?真正做了什么。通過這兩種數據結構共同來管理程序的內存;它們共同來完成解決頁面故障、釋放內存、從內存中交換出數據、等等。下圖是內存分配的一個簡單案例:
?
Example of demand paging and memory allocation
當程序通過?brk()?系統調用來請求一些內存時,內核只是簡單地?更新?堆的 VMA 并給程序回復“已搞定”。而在這個時候并沒有真正地分配頁面幀,并且新的頁面也沒有映射到物理內存上。一旦程序嘗試去訪問這個頁面時,處理器將發生頁面故障,然后調用?do_page_fault()。這個函數將使用?find_vma()?去?搜索?發生頁面故障的 VMA。如果找到了,然后在 VMA 上進行權限檢查以防范惡意訪問(讀取或者寫入)。如果沒有合適的 VMA,也沒有所嘗試訪問的內存的“合約”,將會給進程返回段故障。
當找到了一個合適的 VMA,內核必須通過查找 PTE 的內容和 VMA 的類型去處理故障。在我們的案例中,PTE 顯示這個頁面是?不存在的。事實上,我們的 PTE 是全部空白的(全部都是 0),在 Linux 中這表示虛擬內存還沒有被映射。由于這是匿名 VMA,我們有一個完全的 RAM 事務,它必須被?do_anonymous_page()?來處理,它分配頁面幀,并且用一個 PTE 去映射故障虛擬頁面到一個新分配的幀。
有時候,事情可能會有所不同。例如,對于被交換出內存的頁面的 PTE,在當前(Present)標志位上是 0,但它并不是空白的。而是在交換位置仍有頁面內容,它必須從磁盤上讀取并且通過?do_swap_page()?來加載到一個被稱為?major fault?的頁面幀上。
這是我們通過探查內核的用戶內存管理得出的前半部分的結論。在下一篇文章中,我們通過將文件加載到內存中,來構建一個完整的內存框架圖,以及對性能的影響。
總結
以上是生活随笔為你收集整理的Linux内核如何管理内存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 虚拟地址布局
- 下一篇: Linux中磁盘还有空间,但创建文件时提