引言
Linux 内核的内存管理子系统是整个操作系统的基石,负责管理物理内存的分配、回收、映射与隔离。从用户态的 malloc() 到底层的页面分配器(Page Allocator),这条链路涉及 slab/slub/slob 分配器、伙伴系统(Buddy System)、LRU 页面回收、OOM Killer、CMA 连续内存分配器、KASAN 内存检测等多个核心组件。本文将深入剖析这些机制的原理与实战调优方法。
一、内存管理架构总览
Linux 内核内存管理采用分层架构:
- 用户空间层:glibc 的 malloc/free 通过 brk/mmap 系统调用向内核申请内存
- 内核空间层:kmalloc/kfree 基于 slab/slub 分配器分配连续物理内存;vmalloc/vfree 分配虚拟连续但物理不一定连续的大内存;alloc_pages 直接操作伙伴系统
- 硬件层:MMU 页表转换(多级页表 PGD/P4D/PUD/PMD/PTE)、TLB 缓存、硬件预取
二、伙伴系统(Buddy System)
伙伴系统是物理内存分配的核心,以 page 结构体为最小单位(通常 4KB)。内核将物理内存划分为 11 个 order(0-10),每个 order 管理大小为 2^order 个连续页。
2.1 分配与释放(Coalescing)
分配流程:从目标 order 的空闲链表中取空闲块;若无则向更大 order 递归查找并拆分;拆分时将一半返回,另一半放入低一级 order 链表。
释放流程:检查相邻的"伙伴"页是否也在空闲链表中,若是则合并为更大的块,递归向上合并。
2.2 页面迁移类型(MIGRATE_TYPES)
- MOVABLE:用户页、可移动内核页
- RECLAIMABLE:slab 可回收页
- UNMOVABLE:内核代码、DMA 页、固定映射页
- PCP:Per-CPU hot page cache,避免锁竞争
cat /proc/buddyinfo
Node 0, zone Normal 20 15 8 3 2 1 2 1 1 1 3
三、SLUB 分配器深度剖析
SLUB 是 Linux 现代默认 slab 分配器,相比 SLAB 简化队列管理,多核场景下性能更优。
3.1 三级分配路径
- 快速路径:当前 CPU 的 freelist 直接取空闲对象,无锁 O(1)
- 中速路径:CPU slab 无空闲,从 partial 链表取一个设为当前 CPU slab
- 慢速路径:所有 partial 耗尽,向伙伴系统申请新 page 初始化为 slab
3.2 Red Zone 与 UAF 检测
SLUB 在对象周围放置 Red Zone 区域检测越界写入。释放时将对象加入隔离列表延迟重用,若 Red Zone 被修改则触发告警。
slub_debug=FZP # F=完整性检查 Z=红区 P=中毒
四、LRU 页面回收机制
4.1 双 LRU 链表设计
x86_64 架构下每个 NUMA 节点维护两组 LRU 链表(active/inactive),分为匿名页和文件页:
- 匿名页:进程堆栈、mmap(MAP_ANONYMOUS),回收时必须写入 swap
- 文件页:文件映射、page cache,可直接丢弃或写回脏页
4.2 kswapd 后台回收
kswapd 是内核页面回收守护进程,在内存低于高水位线时自动唤醒。
sysctl -w vm.swappiness=60 # swap 倾向(数据库建议 1-10)
sysctl -w vm.vfs_cache_pressure=100 # cache 回收压力
sysctl -w vm.min_free_kbytes=262144 # 最小空闲内存
4.3 Direct Reclaim(直接回收)
当进程分配的页面低于 min_free_kbytes 时,将陷入直接回收,导致明显的延迟尖刺。
grep -E "pgsteal|pgscan" /proc/vmstat
五、OOM Killer 机制
当系统内存极度紧张且回收无效时,内核触发 OOM 选择进程强制终止。
5.1 oom_badness() 评分算法
按 total_vm × oom_score_adj 从高到低排序选择杀死进程,init 进程和内核线程默认豁免。
echo -1000 > /proc/<pid>/oom_score_adj # 保护关键进程
echo "1" > /sys/fs/cgroup/myapp/memory.oom.group # cgroup 整体 OOM
六、内存碎片整理与 CMA
6.1 Memory Compaction
通过 kcompactd 将 movable 页迁移合并成大块连续内存,解决外部碎片问题。
echo 1 > /proc/sys/vm/compact_memory # 手动触发整理
6.2 CMA 连续内存分配器
为 GPU/DMA 设备预留大块连续内存,同时允许内核在设备不使用时借用。
cat /proc/meminfo | grep CMA
七、KASAN 内存错误检测
KASAN 使用 Shadow Memory 跟踪每字节状态,发现越界、UAF、未初始化使用等问题。KFENCE 是轻量采样替代方案,适合生产环境。
CONFIG_KASAN=y
CONFIG_KFENCE=y
八、cgroup v2 内存控制
echo "2G" > /sys/fs/cgroup/myapp/memory.max
echo "512M" > /sys/fs/cgroup/myapp/memory.low
cat /proc/pressure/memory # PSI 压力指标
九、大页(HugeTLB 与 THP)
大页减少 TLB miss,显著提升内存密集型应用性能。数据库场景建议关闭 THP 避免碎片化延迟。
echo never > /sys/kernel/mm/transparent_hugepage/enabled
十、实战调优案例
案例一:Java 服务 Direct Reclaim 延迟尖刺
- 现象:P99 延迟偶尔飙升至 2s,pgscan_direct 突增
- 解决:swappiness=1 + min_free_kbytes=262144 + JVM -XX:+AlwaysPreTouch
案例二:嵌入式设备 OOM
- 现象:ARM 设备播放视频时随机卡死
- 解决:限制 dirty_ratio/dirty_background_ratio 控制 page cache 上界
十一、eBPF 追踪内存分配
bpftrace -e 'kprobe:__kmem_cache_alloc { @start[tid] = nsecs; }
kretprobe:__kmem_cache_alloc /@start[tid]/ {
@ns = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
总结
Linux 内核内存管理是精密的有机整体:伙伴系统管理物理页面的分配与合并,SLUB 提供高效的小对象分配,LRU 和 kswapd 在内存压力下维持系统稳定运行。理解这些底层机制,才能在面对性能瓶颈和 OOM 故障时做出正确的调优决策。

发表评论 取消回复