引言

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 三级分配路径

  1. 快速路径:当前 CPU 的 freelist 直接取空闲对象,无锁 O(1)
  2. 中速路径:CPU slab 无空闲,从 partial 链表取一个设为当前 CPU slab
  3. 慢速路径:所有 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 故障时做出正确的调优决策。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部