2. Linux 内核内存管理基础(一):虚拟地址空间布局、页表与 MMU、物理内存与虚拟内存映射
内存访问越界是 Linux 内核开发中最常见、最难定位的 Crash 类型之一。要精准分析此类问题,必须深刻理解内核如何管理内存。本章从最基础的虚拟地址空间布局入手,逐步深入到页表转换机制与 MMU 硬件原理,为后续的 Crash 现场分析打下坚实的理论基础。
2.1 虚拟地址空间布局
在 64 位 Linux 系统中,虚拟地址空间被划分为用户态和内核态两大部分。这种划分是 CPU 硬件(MMU)和操作系统共同约定的结果,也是隔离用户程序与内核的关键屏障。
2.1.1 用户态与内核态空间划分
以 x86_64 架构为例,虚拟地址总宽度为 48 位(当前实现),地址空间范围为 0x0000 0000 0000 0000 至 0xFFFF FFFF FFFF FFFF。内核将高 128TB 地址空间留给自身,低 128TB 地址空间留给用户进程。
| 区域 | 地址范围 | 大小 | 主要用途 |
|---|---|---|---|
| 用户空间 | 0x0000 0000 0000 0000 ~ 0x0000 7FFF FFFF FFFF |
128 TB | 用户进程代码、堆、栈、数据段、mmap 映射区 |
| 内核空间 | 0xFFFF 8000 0000 0000 ~ 0xFFFF FFFF FFFF FFFF |
128 TB | 内核代码、直接映射区、vmalloc、模块、固定映射区 |
0xFFFF 8000 0000 0000 以上时,说明异常发生在内核态;若落在用户空间地址范围,则可能是用户态程序触发了系统调用或通过异常陷入内核。地址范围的判断是 Crash 分析的第一步。
2.1.2 内核空间内部布局
内核空间内部进一步细分为多个逻辑区域,每个区域有特定的映射属性和用途。理解这些区域有助于在 Crash 日志中快速定位内存访问的上下文。
2.2 页表与 MMU
虚拟地址到物理地址的转换由 MMU(Memory Management Unit) 硬件完成,转换的依据是操作系统维护的 页表(Page Table)。页表是多级结构,x86_64 使用 4 级页表(未来可扩展至 5 级)。
2.2.1 多级页表结构
一个 48 位的虚拟地址被拆分为 5 个部分,用于索引不同级别的页表项:
- PGD(Page Global Directory):顶级页表,每个进程一个,存储在
mm_struct->pgd中。 - P4D(Page 4th Directory):四级页表目录(4 级页表时与 PGD 合并)。
- PUD(Page Upper Directory):三级页表目录。
- PMD(Page Middle Directory):二级页表目录。
- PTE(Page Table Entry):一级页表项,直接指向物理页帧。
| 虚拟地址位域 | 位范围 | 页表级别 | 索引大小 |
|---|---|---|---|
| PGD 索引 | bit 47:39 | Page Global Directory | 9 bits (512 项) |
| PUD 索引 | bit 38:30 | Page Upper Directory | 9 bits (512 项) |
| PMD 索引 | bit 29:21 | Page Middle Directory | 9 bits (512 项) |
| PTE 索引 | bit 20:12 | Page Table Entry | 9 bits (512 项) |
| 页内偏移 | bit 11:0 | 物理页内偏移 | 12 bits (4KB 页) |
2.2.2 TLB 与缓存
每次内存访问都遍历 4 级页表会带来巨大的性能开销。MMU 内部集成了 TLB(Translation Lookaside Buffer),即页表缓存,用于缓存最近使用的虚拟地址到物理地址的映射关系。
- TLB 命中:直接获得物理地址,无需遍历页表。
- TLB 未命中:硬件遍历页表,更新 TLB,然后重试访问。
- TLB 刷新的触发场景:进程切换(CR3 寄存器写入)、内核修改页表(如
set_pte_at)、内存回收、大页映射等。
2.3 物理内存与虚拟内存映射
物理内存是有限的硬件资源,虚拟内存通过页表机制为每个进程提供独立的、连续的地址空间。内核需要管理物理内存的分配与回收,并建立虚拟地址到物理地址的映射关系。
2.3.1 直接映射区(线性映射)
内核将大部分物理内存直接映射到内核虚拟地址空间的高位区域,称为 直接映射区 或 线性映射区。在 x86_64 上,该区域起始地址为 PAGE_OFFSET(通常为 0xFFFF 8800 0000 0000)。
- 映射关系:虚拟地址 =
PAGE_OFFSET + 物理地址(或减去一个固定偏移)。 - 优点:转换速度快,只需一次加减运算,无需遍历页表(但页表仍需存在以支持 MMU 权限检查)。
- 缺点:占用连续的虚拟地址空间,无法映射超过虚拟地址空间大小的物理内存。
2.3.2 vmalloc 映射区
对于需要非连续物理内存的场景(如大型内核模块、动态分配的大块内存),内核使用 vmalloc 机制。vmalloc 在虚拟地址空间中找到一段连续的区域,然后将不连续的物理页帧映射到该区域。
- 特点:虚拟地址连续,物理地址不连续。
- 性能:每次访问都需要遍历页表,TLB 命中率较低。
- 典型用途:内核模块加载、某些驱动的大块 DMA 缓冲区。
2.3.3 物理内存管理单元
内核使用 伙伴系统(Buddy System) 管理物理页帧的分配与释放,使用 slab/slub 分配器 管理小对象的内存分配。这些分配器返回的物理内存,最终通过页表映射到内核或用户虚拟地址空间。
| 分配器 | 管理对象 | 典型大小 | 映射方式 |
|---|---|---|---|
| 伙伴系统 | 物理页帧(order 0 ~ 10) | 4KB ~ 4MB | 直接映射区或 vmalloc |
| slab/slub | 内核对象(如 task_struct, inode) | 几十字节 ~ 几 KB | 从伙伴系统分配的页帧中切分,映射到直接映射区 |
| vmalloc | 非连续物理内存块 | 任意大小 | vmalloc 映射区 |
2.4 本章小结
- 虚拟地址空间布局:用户态与内核态严格隔离,内核空间内部划分为直接映射区、vmalloc 区、模块区、固定映射区等,每个区域有特定的用途和访问属性。
- 页表与 MMU:x86_64 使用 4 级页表完成虚拟地址到物理地址的转换,TLB 缓存加速地址转换,页表刷新是内存管理的关键操作。
- 物理内存与虚拟内存映射:直接映射区提供快速线性映射,vmalloc 提供非连续物理内存的虚拟连续映射,伙伴系统和 slab/slub 分配器管理物理内存的分配与释放。