📖 推薦閱讀:《Yocto項目實戰教程:高效定制嵌入式Linux系統》
🎥 更多學習視頻請關注 B 站:嵌入式Jerry
深入理解 Slab / Buddy 分配器與 MMU 映射機制
在現代 Linux 內核中,物理內存的管理和虛擬地址的映射是系統性能和資源調度的核心。本文將系統講解 Slab 分配器和 Buddy 分配器如何管理物理內存,并進一步分析 MMU 如何通過四級頁表將這些物理內存映射到虛擬地址空間。
一、Buddy 分配器:以頁為單位的物理內存管理
Buddy 分配器是 Linux 核心的物理頁分配器,它以 2^n 頁為單位分配連續的內存塊,適合于大塊內存分配需求。
基本功能
- 分配單位:1 頁 = 4KB (x86_64)
- 支持 2^n 頁的分配(如 4KB, 8KB, 16KB, …, 1MB 等)
- 重構形成 Buddy 對,便于合并和釋放
示例
請求 16KB 內存:
alloc_pages(GFP_KERNEL, 2) // 2^2 = 4 頁 = 16KB
返回一個連續的 16KB 物理地址指針
二、Slab 分配器:對象級別的內存管理
Slab 分配器用于小塊內存分配,不能簡單通過 buddy 分配器分配不同顏色大小的內存塊。Slab 通常依賴于 buddy 分配器進行基礎頁分配,然后將頁內分割為小對象。
示例
kmalloc(64, GFP_KERNEL) // 分配 64 字節
- slab 分配器會找到 64-byte 的 cache
- 如果無空,則從 buddy 分配 1 頁 (4KB)
- 切割為 64-byte * 64 個對象
三、MMU 與四級頁表映射機制
由于操作系統對多連續和線性虛擬內存的需求,需要通過四級頁表把虛擬地址 (VA) 映射成物理地址 (PA),用于 MMU 轉換。
四級頁表結構詳解(以 x86_64 為例)
頁表級別 | 位數 | 索引項數 | 控制范圍 |
---|---|---|---|
PGD(頂層) | 9 | 512 | 512 GB |
PUD(上層) | 9 | 512 | 1 GB |
PMD(中間) | 9 | 512 | 2 MB |
PTE(底層) | 9 | 512 | 4 KB |
頁內偏移 | 12 | - | 4 KB |
48 位虛擬地址 = 9 + 9 + 9 + 9 + 12
四級頁表拆解結構圖:
|<----- 9 ---->|<---- 9 ---->|<---- 9 ---->|<---- 9 ---->|<-- 12 -->|
+--------------+-------------+-------------+-------------+----------+
| PGD Index | PUD Index | PMD Index | PTE Index | Offset |
+--------------+-------------+-------------+-------------+----------+
- PGD:Page Global Directory(頁全局目錄)
- PUD:Page Upper Directory(頁上級目錄)
- PMD:Page Middle Directory(頁中級目錄)
- PTE:Page Table Entry(頁表項)
- Offset:頁內偏移
四級頁表查找流程圖
graph TD;A[虛擬地址 (VA)] --> B[CR3 - PGD 基地址]B --> C[PGD 索引]C --> D[PUD 索引]D --> E[PMD 索引]E --> F[PTE 索引]F --> G[頁框號 + Offset 得到物理地址]
四級頁表轉換詳細步驟
- CR3 寄存器中保存 PGD(頁全局目錄)的物理地址。
- 取虛擬地址的高 9 位,找到 PGD 的索引項,得到下一級 PUD 的物理地址。
- 取虛擬地址次高 9 位,找到 PUD 的索引項,得到下一級 PMD 的物理地址。
- 再取 9 位,查找 PMD 的索引項,獲得 PTE 的物理地址。
- 最低的 9 位用于查找 PTE 表中的頁表項(含物理頁框號 PFN)。
- 最后的 12 位頁內偏移,加到物理頁框基地址上,形成最終物理地址。
示例:虛擬地址到物理地址
假設虛擬地址為:0x00007F12_3456789A
分解:
- PGD 索引:[47:39] = 0x0F
- PUD 索引:[38:30] = 0x3C
- PMD 索引:[29:21] = 0x15
- PTE 索引:[20:12] = 0x1A
- Offset:[11:0] = 0x89A
假設每級查找后頁表物理地址分別如下:
- PGD[0x0F] -> PUD @ 0x00200000
- PUD[0x3C] -> PMD @ 0x00300000
- PMD[0x15] -> PTE @ 0x00400000
- PTE[0x1A] -> PFN = 0x00500000
最終物理地址:
0x00500000 + 0x89A = 0x0050089A
完整映射流程(結構示意圖):
graph TD;VA[虛擬地址 0x00007F12_3456789A]VA --> PGD[PGD[0x0F] @ 0x00100000]PGD --> PUD[PUD[0x3C] @ 0x00200000]PUD --> PMD[PMD[0x15] @ 0x00300000]PMD --> PTE[PTE[0x1A] @ 0x00400000]PTE --> PA[物理頁框 0x00500000]PA --> FINAL[最終物理地址 0x0050089A]
四、Slab/Buddy 分配與 MMU 映射的關系
-
Buddy 分配器分配物理頁,供:
- 用戶端虛擬內存(進程空間)
- 內核空間內存(kmalloc/slab/cache)
- 內核模塊、頁表、內核堆棧等
-
Slab 分配器依賴 buddy 分配頁,將頁切割為對象,重用已分配物理內存塊
-
MMU + 四級頁表完成虛擬地址空間到這些物理頁的映射。進程或內核訪問虛擬地址時,MMU 通過四級頁表找到實際物理頁,實現隔離、保護和高效內存訪問。
五、核心要點總結
- Buddy / Slab 都分配物理內存,只不過粒度和用途不同
- 虛擬地址通過四級頁表結構映射到物理地址,MMU 保證進程空間隔離和內存保護
- 四級頁表結構的每一級都影響性能和內存消耗,實際 Linux 支持巨頁(1GB/2MB)減少多級查表
六、參考流程圖
graph TD;U[用戶/內核分配虛擬內存] --> K[Slab/Buddy 向內核申請物理頁]K --> MMU[MMU 建立虛擬到物理映射(四級頁表)]MMU --> X[進程/內核通過虛擬地址訪問數據]X --> MMU2[MMU 查頁表,定位物理地址]MMU2 --> RAM[物理內存訪問]
📖 推薦閱讀:《Yocto項目實戰教程:高效定制嵌入式Linux系統》
🎥 更多學習視頻請關注 B 站:嵌入式Jerry