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 或交换回收来缓解压力。

虚拟内存到物理内存的映射流程

  1. 进程访问虚拟地址 0x7f123456。
  2. MMU 查询页表,找到对应的页表项 (PTE)。
  3. 如果 PTE 有效且 Present 位为 1,直接转换为物理地址。
  4. 如果 Present 位为 0,触发缺页异常 (Page Fault),内核分配物理页框并更新页表。
  5. 如果物理内存不足,内核启动回收机制,可能触发 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 页是否被访问 内核回收算法参考此位
虚拟地址到物理地址转换流程(MMU + 页表) 虚拟地址 (VA) MMU (硬件地址转换) PML4 (顶级页表) PDPT (页上级目录) PD (页中间目录) PT (页表) 物理地址 (PA) 说明:MMU 根据虚拟地址的各级索引逐级查询页表,最终得到物理页框号 + 页内偏移。 TLB 缓存最近使用的页表项,避免多次内存访问。

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)

伙伴系统分配流程

  1. 请求分配 2^order 个连续页框。
  2. 从 free_area[order] 链表查找空闲块。
  3. 如果找到,直接分配;否则向上查找 free_area[order+1]。
  4. 将大块分裂为两个伙伴块,一个分配,一个挂入低阶链表。
  5. 释放时检查伙伴是否空闲,如果是则合并回高阶链表。
OOM 关联: 伙伴系统无法满足高阶分配请求时,内核触发直接回收 (direct reclaim) 或 OOM Killer。内存超卖场景下,高阶分配失败是常见 OOM 诱因。
伙伴系统分配与合并流程 初始空闲块 (order=3, 8页框, 32KB) 伙伴 A (order=2, 4页框) 伙伴 B (order=2, 4页框) 分配出去 (order=2, 4页框) 释放后检查伙伴 B 是否空闲 合并回 order=3 空闲链表 伙伴 B 空闲 → 合并;否则不合并

2.3.2 slab 分配器

slab 分配器用于分配内核对象(如 task_struct、inode、dentry),避免伙伴系统直接分配小对象造成的内部碎片。slab 分配器维护对象缓存,支持着色 (coloring) 优化硬件缓存行。

概念 说明 OOM 关联
cache 特定类型对象的缓存池 每个 cache 占用伙伴系统的页框
slab 一个或多个连续页框,划分为多个对象 slab 回收可释放内存
object 内核数据结构实例 对象泄漏导致 slab 内存膨胀
kmem_cache slab 分配器的核心管理结构 通过 /proc/slabinfo 监控

slab 分配器工作流程

  1. 内核请求分配一个对象(如 kmem_cache_alloc(cachep, GFP_KERNEL))。
  2. 从对应 cache 的 空闲链表 获取对象。
  3. 如果空闲链表为空,从伙伴系统分配新的 slab(一个或多个页框)。
  4. 将 slab 划分为对象,初始化后加入空闲链表。
  5. 释放时对象返回空闲链表;如果整个 slab 空闲,释放回伙伴系统。
OOM 陷阱: slab 缓存可能占用大量物理内存。例如,dentry 和 inode 缓存过大时,即使进程内存正常,内核也可能触发 OOM。使用 slabtop 或 /proc/slabinfo 监控 slab 使用量。
slab 分配器结构 kmem_cache slab (满) slab (部分) slab (空闲) obj obj obj obj obj obj 伙伴系统 (页框) 说明:kmem_cache 管理多个 slab,每个 slab 包含多个对象。 分配对象时优先从部分空闲 slab 获取;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 触发。