您當前的位置:首頁 > 攝影

Linux核心是如何管理記憶體的?

作者:由 柴可夫斯貓 發表于 攝影時間:2019-05-29

本文翻譯自

How The Kernel Manages Your Memory

在介紹完程序中虛擬地址空間的佈局後,我們來看一看核心是如何管理記憶體的:

Linux核心是如何管理記憶體的?

核心中使用結構體

task_struct

來描述程序,其中含有一個

mm_struct

型別的成員

mm

,該型別是記憶體管理的主要資料結構,如上圖所示,它儲存著以下內容:

每一個虛擬地址段的起始地址

程序使用的物理頁面 (Physical Page) 的數量

程序使用的虛擬地址空間(Virtual Address Space)的數量

其他額外的資訊

還有兩個和記憶體管理相關的重要概念:Virtual Memory Area 和 Page Table

Linux核心是如何管理記憶體的?

每一個 Virtual Memory Area (以下簡稱 VMA)是一段連續且不重疊的虛擬地址,核心用

vm_area_struct

來描述 VMA,它記錄著如下資訊:

VMA 起始地址

訪問許可權的標記

對映檔案(如果有的話。沒有對映檔案的 VMA 是匿名對映)

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

一個程序的所有 VMA 都被儲存在它的記憶體描述符中的一個已排序(根據虛擬地址的起始地址)的連結串列(在mmap欄位中)和紅黑樹(mm_rb)中。使用紅黑樹可以使核心快速查詢一給定地址所在的 VMA 中。透過檢視

/proc/pid_of_process/maps

檔案,核心會遍歷該程序的 VMA 連結串列並打印出來。

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

4GB 的虛擬地址空間被分割成 頁(Page),x86 的32位處理器支援 4KB、2MB 和 4MB 大小的頁,Linux 和 Windows 都預設使用 4KB 大小的頁。一個 VMA 的大小必須是頁大小的整數倍。下圖是 3GB 大小使用者空間的 Page 示例圖:

Linux核心是如何管理記憶體的?

處理器使用頁表(Page Table)來將虛擬地址轉換為物理地址,每一個程序都維護著它自己的頁表,當程序切換時,對應的頁表(使用者空間的)也會發生切換。Linux 在記憶體描述符使用

pgd

欄位指向該程序的頁表,每 一個虛擬頁面都對應著一個頁表項(Page Table Entry,PTE),在x86機器上如下所示:

Linux核心是如何管理記憶體的?

PTE 中有很多 flag,Linux 使用專門的函式來設定和讀取這些 flag。

P 標誌位表示該頁面是否已經對映到物理記憶體,如果該位為0,訪問該頁將觸發缺頁中斷,Keep in mind that when this bit is zero,

the kernel can do whatever it pleases

with the remaining fields。

R/W 標誌位表示該頁的讀寫屬性,0為只讀;

U/S 表示該頁的訪問許可權,0表示只能被核心訪問。

以上這些標誌位實現了記憶體的防寫和核心態記憶體空間。

D - Dirty,表示髒頁面,如果一個頁面被寫過,它的 D 位被置為 1;

A - Access,如果該頁面被訪問過(讀或寫),它的 A 位被置為 1。

這些標誌位可以被 CPU 設定,但是隻能由核心去 clear 。

最後,PTE 儲存著該頁面對應的 4K 對齊的起始物理地址。通常頁面的最大對映範圍是 4GB 的物理記憶體,但是可以透過 PTE 的其他位進行拓展。

一個虛擬頁面是記憶體保護的基本單位,因為如果從物理頁的角度看,同一個物理頁可以對應多個虛擬頁,從而有不同的保護標誌位。注意 PTE 沒有關於執行許可權的標記,所以在經典 x86 體系機中允許棧上的程式碼被執行,這容易引致緩衝區溢位攻擊。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。

虛擬頁面不實際儲存資料,它只是將程序的地址空間對映到底層的物理記憶體。

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

Linux核心是如何管理記憶體的?

在 Linux 中使用一個描述符和幾個標記位來表示頁幀,這些描述符整體描述了整個的物理記憶體,一個頁幀的狀態總是確定且已知的。物理記憶體透過夥伴演算法進行管理和分配,如果一個頁楨當前可透過夥伴演算法被分配,則稱它是 free 的。

一個已分配的頁楨有兩種狀態:一種是“匿名”的,存放著程式的資料;另一種情況在 page cache 中,存放著

檔案

塊裝置

中的資料(暫不考慮其他奇怪的用法)。Windows 使用 PFN (Page Frame Number) 資料庫來管理物理記憶體。

下面這個圖描述了 虛擬記憶體空間(VMA),頁表項和頁楨的關係:

Linux核心是如何管理記憶體的?

中間藍色的方塊表示 VMA 中的 page,它的箭頭表示透過頁表項指向了物理頁楨。其中一部分 page 是沒有箭頭的,這意味著其頁表項中的P(resent)標記位被清零,這可能是由於該頁不再被使用或其內容已經被 swaped out,這兩種情況下訪問該頁都會導致缺頁中斷。

VMA 像是核心和上層應用的橋樑,上層應用向核心請求做一件事(記憶體分配、檔案對映等),核心會直接同意,然後修改 VMA,但是核心不會立即去執行上層應用請求的事,真正的執行請求需要一個缺頁中斷來做。從這個角度看,核心懶惰且不誠實,但這也是 virtual memory 的基準準則。VMA 記錄著核心已經

“同意”

的事,而頁表項記錄著核心已經

“做成”

的事。這兩個結構是記憶體管理的主要成員。下面是一個記憶體分配的流程示例:

Linux核心是如何管理記憶體的?

應用程式透過 brk() 系統呼叫來申請更多記憶體時後,核心擴充套件 heap VMA,但新擴充套件的部分還沒有對應上物理記憶體;當應用程式訪問新申請的記憶體後,系統呼叫 do_page_fault() 會被呼叫,它會透過 find_vma() 在 VMA 中查詢沒有對應上物理記憶體的 virtual address,如果找到的話,會檢查該 VMA 的相關許可權並進行下一步操作*,否則會造成 Segmentation Fault。

上面說的“下一步操作”即查詢頁表項,在上圖的情況下,其頁表項的 P 標記位會顯示其 Not Pressent,(實際上整個頁表項是 blank 的,即全為0)。由於當前 VMA 是匿名的,後面會透過 do_anonymous_page() 來進行頁楨的分配和更新頁表項。

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

標簽: VMA  記憶體  核心  頁表  頁面