深入理解 Slab / Buddy 分配器与 MMU 映射机制
📖 推荐阅读:《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