2. 内存管理基础回顾:虚拟内存与物理内存、页表与MMU、内存分配路径(伙伴系统与slab分配器)
在深入 OOM Killer 调优之前,必须牢固掌握 Linux 内存管理的核心基石。本章将回顾虚拟内存与物理内存的映射关系、页表与 MMU 的地址转换机制,以及内核的两大内存分配器——伙伴系统与 slab 分配器。这些概念是理解内存超卖和 OOM 触发条件的根本前提。
2.1 虚拟内存与物理内存
Linux 采用虚拟内存管理,每个进程拥有独立的 4GB(32位)或 128TB(64位)虚拟地址空间。虚拟地址通过页表映射到物理内存,实现隔离、共享与按需分配。
| 概念 | 说明 | 关键特性 |
|---|---|---|
| 虚拟地址 (VA) | 进程看到的线性地址空间 | 连续、独立、可大于物理内存 |
| 物理地址 (PA) | 硬件总线上的真实内存地址 | 有限、离散、被所有进程共享 |
| 页框 (Page Frame) | 物理内存的最小分配单元(通常 4KB) | 由伙伴系统管理 |
| 页 (Page) | 虚拟内存的最小单元 | 映射到页框或交换空间 |
核心要点: 虚拟内存允许进程“超卖”内存——承诺的内存总量可以超过物理 RAM。当物理内存耗尽时,内核通过 OOM Killer 或交换回收来缓解压力。
虚拟内存到物理内存的映射流程
- 进程访问虚拟地址
0x7f123456。 - MMU 查询页表,找到对应的页表项 (PTE)。
- 如果 PTE 有效且 Present 位为 1,直接转换为物理地址。
- 如果 Present 位为 0,触发缺页异常 (Page Fault),内核分配物理页框并更新页表。
- 如果物理内存不足,内核启动回收机制,可能触发 OOM Killer。
OOM 关联: 当物理内存耗尽且交换空间不足时,缺页异常无法分配新页框,内核进入 OOM 路径。
2.2 页表与 MMU
页表是虚拟地址到物理地址的转换表,MMU(内存管理单元)是硬件执行单元。Linux 使用多级页表(x86-64 为 4 级页表)来节省内存。
多级页表结构
虚拟地址分解(x86-64 4KB 页):
| 47:39 (9 bit) | 38:30 (9 bit) | 29:21 (9 bit) | 20:12 (9 bit) | 11:0 (12 bit) |
| PML4 索引 | PDPT 索引 | PD 索引 | PT 索引 | 页内偏移 |
- PML4: 页全局目录(顶级)
- PDPT: 页上级目录
- PD: 页中间目录
- PT: 页表
- 偏移: 页内字节偏移
性能陷阱: 多级页表查询需要多次内存访问。TLB(转译后备缓冲器)缓存最近使用的页表项,TLB 缺失会严重降低性能。大页(HugePages)可以减少页表级数和 TLB 压力。
页表项 (PTE) 关键标志位
| 标志位 | 含义 | OOM 关联 |
|---|---|---|
| Present | 页是否在物理内存中 | 为 0 时触发缺页,可能引发 OOM |
| RW | 读写权限 | 写时复制 (COW) 机制依赖此位 |
| User/Supervisor | 用户态/内核态访问控制 | 内核内存不可被用户态直接访问 |
| Dirty | 页是否被写入 | 脏页需要回写交换或文件 |
| Accessed | 页是否被访问 | 内核回收算法参考此位 |
2.3 内存分配路径:伙伴系统与 slab 分配器
Linux 内核有两套内存分配器,分别服务于不同粒度的需求:伙伴系统 (Buddy System) 管理物理页框,slab 分配器 管理内核对象。
2.3.1 伙伴系统 (Buddy System)
伙伴系统以 2 的幂次阶 (order) 为单位管理空闲页框,解决外部碎片问题。核心数据结构是 free_area[] 数组,每个元素维护一个链表,链接对应阶的空闲页块。
| 阶 (Order) | 页框数 | 大小 | 典型用途 |
|---|---|---|---|
| 0 | 1 | 4 KB | 匿名页、文件页 |
| 1 | 2 | 8 KB | 小内核数据结构 |
| 3 | 8 | 32 KB | task_struct、mm_struct |
| 9 | 512 | 2 MB | 透明大页 (THP) |
| 10 | 1024 | 4 MB | 连续内存分配 (DMA) |
伙伴系统分配流程
- 请求分配
2^order个连续页框。 - 从
free_area[order]链表查找空闲块。 - 如果找到,直接分配;否则向上查找
free_area[order+1]。 - 将大块分裂为两个伙伴块,一个分配,一个挂入低阶链表。
- 释放时检查伙伴是否空闲,如果是则合并回高阶链表。
OOM 关联: 伙伴系统无法满足高阶分配请求时,内核触发直接回收 (direct reclaim) 或 OOM Killer。内存超卖场景下,高阶分配失败是常见 OOM 诱因。
2.3.2 slab 分配器
slab 分配器用于分配内核对象(如 task_struct、inode、dentry),避免伙伴系统直接分配小对象造成的内部碎片。slab 分配器维护对象缓存,支持着色 (coloring) 优化硬件缓存行。
| 概念 | 说明 | OOM 关联 |
|---|---|---|
| cache | 特定类型对象的缓存池 | 每个 cache 占用伙伴系统的页框 |
| slab | 一个或多个连续页框,划分为多个对象 | slab 回收可释放内存 |
| object | 内核数据结构实例 | 对象泄漏导致 slab 内存膨胀 |
| kmem_cache | slab 分配器的核心管理结构 | 通过 /proc/slabinfo 监控 |
slab 分配器工作流程
- 内核请求分配一个对象(如
kmem_cache_alloc(cachep, GFP_KERNEL))。 - 从对应 cache 的 空闲链表 获取对象。
- 如果空闲链表为空,从伙伴系统分配新的 slab(一个或多个页框)。
- 将 slab 划分为对象,初始化后加入空闲链表。
- 释放时对象返回空闲链表;如果整个 slab 空闲,释放回伙伴系统。
OOM 陷阱: slab 缓存可能占用大量物理内存。例如,
dentry 和 inode 缓存过大时,即使进程内存正常,内核也可能触发 OOM。使用 slabtop 或 /proc/slabinfo 监控 slab 使用量。
2.4 内存分配路径总结
内核内存分配路径可概括为:
用户态/内核态请求内存
│
▼
伙伴系统 (alloc_pages / __get_free_pages)
│
├── 分配页框 → 直接返回
│
└── 分配失败
│
├── 快速回收 (PF_MEMALLOC)
├── 直接回收 (shrink_node)
└── OOM Killer (out_of_memory)
│
▼
选择 victim 进程 → 发送 SIGKILL
调优启示: 理解伙伴系统和 slab 分配器是 OOM 调优的基础。监控
/proc/buddyinfo 和 /proc/slabinfo 可以提前发现内存碎片或 slab 泄漏,避免 OOM 触发。