內(nèi)核是如何管理內(nèi)存的?

本文翻譯自 How The Kernel Manages Your Memory

在介紹完進(jìn)程中虛擬地址空間的布局后,我們來看一看內(nèi)核是如何管理內(nèi)存的:

image

內(nèi)核中使用結(jié)構(gòu)體 task_struct 來描述進(jìn)程,其中含有一個(gè) mm_struct 類型的成員 mm,該類型是內(nèi)存管理的主要數(shù)據(jù)結(jié)構(gòu),如上圖所示,它存儲(chǔ)著以下內(nèi)容:

  • 每一個(gè)虛擬地址段的起始地址
  • 進(jìn)程使用的物理頁面 (Physical Page) 的數(shù)量
  • 進(jìn)程使用的虛擬地址空間(Virtual Address Space)的數(shù)量
  • 其他額外的信息

還有兩個(gè)和內(nèi)存管理相關(guān)的重要概念:Virtual Memory Area 和 Page Table

image

每一個(gè) Virtual Memory Area (以下簡稱 VMA)是一段連續(xù)且不重疊的虛擬地址,內(nèi)核用 vm_area_struct 來描述 VMA,它記錄著如下信息:

  • VMA 起始地址
  • 訪問權(quán)限的標(biāo)記
  • 映射文件(如果有的話。沒有映射文件的 VMA 是匿名映射)

在上圖中,每一個(gè) Memory Segment (如 heap,stack 等) 都對應(yīng)著一個(gè) VMA。

一個(gè)進(jìn)程的所有 VMA 都被保存在它的內(nèi)存描述符中的一個(gè)已排序(根據(jù)虛擬地址的起始地址)的鏈表(在mmap字段中)和紅黑樹(mm_rb)中。使用紅黑樹可以使內(nèi)核快速查找一給定地址所在的 VMA 中。通過查看 /proc/pid_of_process/maps 文件,內(nèi)核會(huì)遍歷該進(jìn)程的 VMA 鏈表并打印出來。

在 Windows 中,EPROCESS block 大致是 task_struct 和 mm_struct 的混合體。存儲(chǔ) VMA 的結(jié)構(gòu)在 Windows 中叫 VAD(Virtual Address Descriptor),VAD 存放在一棵 AVL 樹中。

4GB 的虛擬地址空間被分割成 頁(Page),x86 的32位處理器支持 4KB、2MB 和 4MB 大小的頁,Linux 和 Windows 都默認(rèn)使用 4KB 大小的頁。一個(gè) VMA 的大小必須是頁大小的整數(shù)倍。下圖是 3GB 大小用戶空間的 Page 示例圖:

image

處理器使用頁表(Page Table)來將虛擬地址轉(zhuǎn)換為物理地址,每一個(gè)進(jìn)程都維護(hù)著它自己的頁表,當(dāng)進(jìn)程切換時(shí),對應(yīng)的頁表(用戶空間的)也會(huì)發(fā)生切換。Linux 在內(nèi)存描述符使用 pgd 字段指向該進(jìn)程的頁表,每 一個(gè)虛擬頁面都對應(yīng)著一個(gè)頁表項(xiàng)(Page Table Entry,PTE),在x86機(jī)器上如下所示:

image

PTE 中有很多 flag,Linux 使用專門的函數(shù)來設(shè)置和讀取這些 flag。

  • P 標(biāo)志位表示該頁面是否已經(jīng)映射到物理內(nèi)存,如果該位為0,訪問該頁將觸發(fā)缺頁中斷,Keep in mind that when this bit is zero, the kernel can do whatever it pleases with the remaining fields.
  • R/W 標(biāo)志位表示該頁的讀寫屬性,0為只讀;
  • U/S 表示該頁的訪問權(quán)限,0表示只能被內(nèi)核訪問。

以上這些標(biāo)志位實(shí)現(xiàn)了內(nèi)存的寫保護(hù)和內(nèi)核態(tài)內(nèi)存空間。

  • D - Dirty,表示臟頁面,如果一個(gè)頁面被寫過,它的 D 位被置為 1;
  • A - Access,如果該頁面被訪問過(讀或?qū)懀?,它?A 位被置為 1。

這些標(biāo)志位可以被 CPU 設(shè)置,但是只能由內(nèi)核去 clear 。

最后,PTE 存儲(chǔ)著該頁面對應(yīng)的 4K 對齊的起始物理地址。通常頁面的最大映射范圍是 4GB 的物理內(nèi)存,但是可以通過 PTE 的其他位進(jìn)行拓展。

一個(gè)虛擬頁面是內(nèi)存保護(hù)的基本單位,因?yàn)槿绻麖奈锢眄摰慕嵌瓤?,同一個(gè)物理頁可以對應(yīng)多個(gè)虛擬頁,從而有不同的保護(hù)標(biāo)志位。注意 PTE 沒有關(guān)于執(zhí)行權(quán)限的標(biāo)記,所以在經(jīng)典 x86 體系機(jī)中允許棧上的代碼被執(zhí)行,這容易引致緩沖區(qū)溢出攻擊。This lack of a PTE no-execute flag illustrates a broader fact: permission flags in a VMA may or may not translate cleanly into hardware protection. The kernel does what it can, but ultimately the architecture limits what is possible.

虛擬頁面不實(shí)際存儲(chǔ)數(shù)據(jù),它只是將進(jìn)程的地址空間映射到底層的物理內(nèi)存。

在總線上的內(nèi)存操作比較復(fù)雜, 我們可以假設(shè)物理地址區(qū)間是從0到可用內(nèi)存按照字節(jié)自增的。物理地址空間被內(nèi)核以頁大小為單位劃分頁幀(Frame)。CPU不知道頁幀的存在, 但是頁幀是對內(nèi)核卻很重要, 也是內(nèi)核內(nèi)存管理最基本的單元。 Linux和Windows都使用4KB大小的頁幀(32位模式);下圖是一個(gè)擁有2G內(nèi)存的例子:

image

在 Linux 中使用一個(gè)描述符和幾個(gè)標(biāo)記位來表示頁幀,這些描述符整體描述了整個(gè)的物理內(nèi)存,一個(gè)頁幀的狀態(tài)總是確定且已知的。物理內(nèi)存通過伙伴算法進(jìn)行管理和分配,如果一個(gè)頁楨當(dāng)前可通過伙伴算法被分配,則稱它是 free 的。

一個(gè)已分配的頁楨有兩種狀態(tài):一種是“匿名”的,存放著程序的數(shù)據(jù);另一種情況在 page cache 中,存放著 *文件 *或 *塊設(shè)備 *中的數(shù)據(jù)(暫不考慮其他奇怪的用法)。Windows 使用 PFN (Page Frame Number) 數(shù)據(jù)庫來管理物理內(nèi)存。

下面這個(gè)圖描述了 虛擬內(nèi)存空間(VMA),頁表項(xiàng)和頁楨的關(guān)系:

image

中間藍(lán)色的方塊表示 VMA 中的 page,它的箭頭表示通過頁表項(xiàng)指向了物理頁楨。其中一部分 page 是沒有箭頭的,這意味著其頁表項(xiàng)中的P(resent)標(biāo)記位被清零,這可能是由于該頁不再被使用或其內(nèi)容已經(jīng)被 swaped out,這兩種情況下訪問該頁都會(huì)導(dǎo)致缺頁中斷。

VMA 像是內(nèi)核和上層應(yīng)用的橋梁,上層應(yīng)用向內(nèi)核請求做一件事(內(nèi)存分配、文件映射等),內(nèi)核會(huì)直接同意,然后修改 VMA,但是內(nèi)核不會(huì)立即去執(zhí)行上層應(yīng)用請求的事,真正的執(zhí)行請求需要一個(gè)缺頁中斷來做。從這個(gè)角度看,內(nèi)核懶惰且不誠實(shí),但這也是 virtual memory 的基準(zhǔn)準(zhǔn)則。VMA 記錄著內(nèi)核已經(jīng)“同意”的事,而頁表項(xiàng)記錄著內(nèi)核已經(jīng)“做成”的事。這兩個(gè)結(jié)構(gòu)是內(nèi)存管理的主要成員。下面是一個(gè)內(nèi)存分配的流程示例:

內(nèi)存分配流程

應(yīng)用程序通過 brk() 系統(tǒng)調(diào)用來申請更多內(nèi)存時(shí)后,內(nèi)核擴(kuò)展 heap VMA,但新擴(kuò)展的部分還沒有對應(yīng)上物理內(nèi)存;當(dāng)應(yīng)用程序訪問新申請的內(nèi)存后,系統(tǒng)調(diào)用 do_page_fault() 會(huì)被調(diào)用,它會(huì)通過 find_vma() 在 VMA 中查找沒有對應(yīng)上物理內(nèi)存的 virtual address,如果找到的話,會(huì)檢查該 VMA 的相關(guān)權(quán)限并進(jìn)行下一步操作*,否則會(huì)造成 Segmentation Fault.

上面說的“下一步操作”即查找頁表項(xiàng),在上圖的情況下,其頁表項(xiàng)的 P 標(biāo)記位會(huì)顯示其 Not Pressent,(實(shí)際上整個(gè)頁表項(xiàng)是 blank 的,即全為0)。由于當(dāng)前 VMA 是匿名的,后面會(huì)通過 do_anonymous_page() 來進(jìn)行頁楨的分配和更新頁表項(xiàng)。

這一步操作也會(huì)有另外一種情況,即頁表項(xiàng)的 P 標(biāo)記位為0,但整個(gè)頁表項(xiàng)不是 blank 的,這意味著它之前被 swaped out,這種情況可以在頁表項(xiàng)中找到其 swap 地址,之后由 do_swap_pages() 來處理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容