2. 内核内存管理基础:伙伴系统、slab分配器、vmalloc区域、内存回收机制
在深入使用 SystemTap 调试内核内存泄漏之前,必须透彻理解 Linux 内核的内存管理架构。内存泄漏的本质是“分配后未释放”,而泄漏可能发生在不同的内存管理层级。本章将拆解内核内存管理的四大核心子系统:伙伴系统(Buddy System)、slab 分配器、vmalloc 区域以及内存回收机制,并指出每一层可能出现的泄漏模式。
调试视角: SystemTap 探测点通常挂载在内存分配与释放的路径上。理解底层数据结构(如
struct page、kmem_cache、vm_struct)是编写精准探测脚本的前提。
2.1 伙伴系统(Buddy System)
伙伴系统是内核最底层的物理页面管理器,负责以 2 的幂次阶(order)为单位分配连续的物理页框。其核心数据结构是 free_area 数组,每个元素维护一个空闲链表。
核心数据结构
- struct page:描述每个物理页框的元数据,包含
_refcount(引用计数)、flags、mapping等字段。 - free_area[MAX_ORDER]:每个阶(0~10)对应一个链表,存储该阶的空闲连续页块。
- zone:将物理内存划分为 DMA、Normal、HighMem 等区域,每个 zone 拥有独立的伙伴系统。
泄漏模式: 如果驱动调用
alloc_pages(gfp_mask, order) 后未调用 __free_pages(page, order),会导致该阶的连续页块永久丢失。SystemTap 可通过探测 mm_page_alloc 和 mm_page_free tracepoint 来追踪未配对的分配。
2.2 slab 分配器
slab 分配器构建在伙伴系统之上,用于管理内核中频繁分配/释放的小对象(如 task_struct、inode、dentry)。它通过缓存(kmem_cache)维护预先分配的对象池,避免直接操作伙伴系统带来的性能开销。
关键概念对比
| 概念 | 说明 | SystemTap 探测点 |
|---|---|---|
| kmem_cache | 管理特定类型对象的缓存,包含多个 slab | kernel.trace("kmem_cache_alloc") |
| slab | 一个或多个连续物理页框,划分为若干对象 | kernel.trace("kmem_cache_free") |
| object | 实际分配的内核对象实例 | 通过 $ptr 追踪地址 |
| 着色(coloring) | 通过偏移调整缓存行对齐,减少伪共享 | 通常不直接探测 |
常见泄漏场景: 内核模块反复调用
kmem_cache_alloc() 但遗漏 kmem_cache_free(),导致特定 kmem_cache 的 slab_objects 持续增长。使用 SystemTap 统计 kmem_cache_alloc 与 kmem_cache_free 的差值即可定位泄漏的缓存。
2.3 vmalloc 区域
vmalloc 用于分配虚拟地址连续但物理地址不连续的内存区域,适用于大块内存分配(如内核模块加载、动态缓冲区)。其虚拟地址范围通常位于 VMALLOC_START 到 VMALLOC_END 之间,通过页表建立非连续物理页的映射。
vmalloc 与 kmalloc 对比
| 特性 | kmalloc | vmalloc |
|---|---|---|
| 物理连续性 | 连续 | 不连续 |
| 分配大小 | 通常 < 4MB(受限于伙伴系统) | 可分配大块(可达数百 MB) |
| 性能 | 快速(slab 缓存) | 较慢(需建立页表) |
| 内存碎片影响 | 易受外部碎片影响 | 不受外部碎片影响 |
| 典型用途 | 内核数据结构、网络缓冲区 | 动态模块、大块 I/O 缓冲区 |
泄漏模式: 调用
vmalloc(size) 后未调用 vfree(addr),会导致虚拟地址空间泄漏以及物理页引用计数无法归零。SystemTap 可通过探测 vmalloc 和 vfree 函数入口/返回来追踪泄漏。
2.4 内存回收机制
当系统内存压力增大时,内核启动内存回收以释放可回收页面。主要回收路径包括:
- 页面回收(Page Reclaim):通过 LRU 链表扫描匿名页和文件页,将脏页写回、干净页直接回收。
- slab 收缩(Slab Shrinker):调用注册的
shrinker回调,回收 dentry、inode 等可回收 slab 对象。 - OOM Killer:当回收无法满足需求时,选择并杀死占用内存最多的进程。
泄漏与回收的混淆: 内存泄漏会导致回收机制持续触发,但回收无法释放“已泄漏”的内存(因为泄漏的内存没有合法引用,无法被扫描到)。SystemTap 可以通过监测
kswapd 唤醒频率和 shrink_slab 调用次数来间接判断是否存在泄漏。
2.5 各层级泄漏检测要点总结
| 层级 | 分配函数 | 释放函数 | SystemTap 关键探测点 | 泄漏特征 |
|---|---|---|---|---|
| 伙伴系统 | alloc_pages() |
__free_pages() |
mm_page_alloc, mm_page_free |
特定 order 的 free_area 持续减少 |
| slab 分配器 | kmem_cache_alloc() |
kmem_cache_free() |
kmem_cache_alloc, kmem_cache_free |
特定缓存 slab_objects 只增不减 |
| vmalloc | vmalloc() |
vfree() |
vmalloc 函数入口/返回 |
/proc/vmallocinfo 条目持续增长 |
| 内存回收 | — | — | mm_vmscan_kswapd_wake, mm_shrink_slab |
回收频率异常高但无法释放内存 |
实战建议: 在编写 SystemTap 脚本前,先通过
/proc/buddyinfo、/proc/slabinfo、/proc/vmallocinfo 获取基线数据。这些文件直接反映了各层级的内存使用状态,是判断泄漏的第一手证据。