2. Linux 内存管理简介:虚拟内存、物理内存、页表、伙伴系统、slab分配器
在深入内存调试与 kmemleak 泄漏检测之前,必须理解 Linux 内核如何管理内存。本章将拆解从虚拟地址到物理页框的完整路径,并介绍内核分配内存的两大核心机制:伙伴系统与 slab 分配器。这些知识是理解内存泄漏根源的基础。
2.1 虚拟内存与物理内存
Linux 为每个进程提供独立的 虚拟地址空间(Virtual Address Space),使进程误以为自己独占整个内存。虚拟地址通过硬件 MMU(内存管理单元)和内核页表转换为真实的 物理地址(Physical Address)。
| 概念 | 说明 | 典型大小(64位) |
|---|---|---|
| 虚拟内存 | 进程看到的线性地址空间,通过页表映射到物理内存或磁盘交换区 | 128 TB(用户空间) + 128 TB(内核空间) |
| 物理内存 | 硬件 RAM 的实际字节单元,内核以页框(Page Frame)为单位管理 | 取决于硬件安装容量 |
| 页框 | 物理内存的最小分配单元,通常为 4 KB | 4 KB(标准页) / 2 MB(大页) |
调试要点: kmemleak 扫描的是内核对象的虚拟地址,但最终泄漏的是物理页框。理解虚拟到物理的映射,有助于定位泄漏对象所在的物理区域。
2.2 页表与地址转换
Linux 在 x86-64 架构下使用 4 级页表(PGD → PUD → PMD → PTE)。每个进程拥有自己的页表,内核通过 struct mm_struct 管理。页表项(PTE)不仅包含物理页框号,还包含 Present、Dirty、Accessed 等标志位。
// 页表项结构示意(arch/x86/include/asm/pgtable_types.h)
typedef struct { unsigned long pte; } pteval_t;
// 关键标志位
#define _PAGE_PRESENT (1 << 0) // 页在物理内存中
#define _PAGE_RW (1 << 1) // 可写
#define _PAGE_ACCESSED (1 << 5) // 已访问
#define _PAGE_DIRTY (1 << 6) // 已脏
调试关联: 当 kmemleak 报告一个泄漏对象时,可以通过
/proc/self/pagemap 或 pagemap 接口反向查找该虚拟地址对应的物理页框,确认泄漏页是否被意外保留。
2.3 伙伴系统(Buddy System)
伙伴系统是内核管理物理页框的核心算法,用于分配连续的物理内存块。它将空闲页框按 2 的幂次阶(order) 组织成链表,分配时从高阶切分,释放时合并相邻的“伙伴”块。
| 阶(Order) | 连续页框数 | 大小(4KB 页) | 典型用途 |
|---|---|---|---|
| 0 | 1 | 4 KB | slab 分配器、小对象 |
| 1 | 2 | 8 KB | 小缓冲区 |
| 3 | 8 | 32 KB | 文件系统元数据 |
| 10 | 1024 | 4 MB | 大块 DMA 缓冲区 |
2.4 slab 分配器
伙伴系统以页框为单位分配,对于内核中大量的小对象(如 task_struct、inode、dentry)效率太低。slab 分配器在伙伴系统之上,为每种对象类型维护一个 缓存(cache),实现对象复用和高速分配。
- slab 缓存:每个缓存管理一种固定大小的对象,如
kmalloc-32、kmalloc-128。 - slab 节点:每个缓存包含多个 slab,每个 slab 由一个或多个连续页框组成。
- 对象着色(coloring):通过偏移调整对象在 slab 内的起始位置,优化 CPU 缓存行对齐。
// 查看 slab 缓存信息(/proc/slabinfo)
# cat /proc/slabinfo | head -5
slabinfo - version: 2.1
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> ...
kmalloc-256 1024 2048 256 16 1
task_struct 512 512 3456 4 4
inode_cache 2048 4096 640 12 2
泄漏高发区: slab 缓存是 kmemleak 的重点监控对象。如果驱动或内核模块分配了
kmalloc 对象但未释放,该对象所在的 slab 缓存会持续增长,最终导致内存耗尽。通过 /proc/slabinfo 观察异常增长的缓存,可以快速定位泄漏源。
2.5 内存管理路径总结
从进程申请内存到最终物理页框的完整路径如下:
- 用户态:
malloc()→brk()/mmap()→ 内核虚拟内存区域(VMA) - 内核态:
kmalloc()/vmalloc()→ slab 分配器 / 伙伴系统 - 缺页异常:访问未映射页 → 内核分配物理页框 → 填写页表项
- 释放:
kfree()→ slab 回收 → 伙伴系统合并 → 物理页框归还
实战关联: kmemleak 在步骤 2 和步骤 4 之间进行扫描。如果
kmalloc 分配的对象指针丢失(没有 kfree 且没有其他引用),kmemleak 会将其标记为“泄漏”。理解 slab 和伙伴系统的层级关系,有助于判断泄漏对象属于哪个缓存、占用多少物理页框。