引言

Linux 内核的内存管理是操作系统最核心的子系统之一。无论是容器化部署中的 OOM 问题、数据库场景的 HugePages 调优、还是高性能计算中的 NUMA 亲和性,都直接依赖对内核内存管理机制的理解。本文将从伙伴系统(Buddy System)出发,完整剖析 Slab/SLUB 分配器、反向映射与页面回收算法、OOM Killer 选择策略、HugePages 与透明大页、KSM 内存去重以及 NUMA 内存策略,并在每一节给出生产环境中的调优命令、参数配置和排错案例。

一、Buddy System 伙伴系统

1.1 核心原理

伙伴系统是 Linux 内核管理物理页面的基础框架。内核将物理内存划分为多个区域(Zone),如 ZONE_DMA、ZONE_DMA32、ZONE_NORMAL、ZONE_MOVABLE,每个区域维护 11 个空闲页面链表(order 0 到 order 10),分别管理 1、2、4、...、1024 个连续物理页面。

分配时从对应 order 的链表中取出一个页面块;若该 order 无空闲,则从更高 order 拆分。释放时检查"伙伴"(地址连续、相同大小)是否空闲,若空闲则合并到更高 order。这种分配策略保证了 O(1) 的分配效率和最小的外部碎片。

1.2 碎片问题与解决方案

长期运行后,伙伴系统会产生外部碎片——空闲页面总量充足但无足够大的连续物理块。内核通过以下手段缓解:

  • 可移动/不可移动页面分类:ZONE_MOVABLE 专门存放可回收和可移动页面,确保在需要大块连续内存时有足够的可迁移页面。
  • 页面迁移(Page Migration): compaction 机制在分配失败时将可移动页面搬移,腾出连续物理块。/proc/sys/vm/extfrag_threshold 控制碎片指数阈值。
  • order-0 页面隔离:将不可移动的 order-0 页面(如内核栈)与可移动页面隔离,防止大块分配被打断。

1.3 实战:查看伙伴系统状态

cat /proc/buddyinfo
Node 0, zone Normal 12 8 4 2 1 3 2 1 1 0 1

cat /proc/pagetypeinfo | head -30

/proc/buddyinfo 显示每个 Zone 各 order 的空闲块数量。如果高位 order 持续为 0 说明碎片严重。

二、Slab / SLUB 分配器

2.1 为什么需要 Slab

伙伴系统以页面(通常 4KB)为粒度分配。但内核大部分内存请求远小于 4KB(如 task_struct 约 7KB、inode 约 600B)。如果直接通过伙伴系统分配会产生严重的内部碎片。Slab 分配器在伙伴系统之上构建对象缓存,预先分配多个对象在页面中,实现高效的小对象分配。

2.2 SLUB 分配器架构

SLUB(Unqueued Slab Allocator)是当前 Linux 默认的 Slab 实现,相比 SLAB 简化了每-CPU 队列和 NUMA 节点队列的管理:

  • kmem_cache:每种对象类型对应一个 kmem_cache,维护对象大小、对齐方式、构造函数等元数据。
  • CPU Partial 链表:每个 CPU 维护一个专属的 slab 列表,分配/释放无锁操作,性能最优。
  • Node Partial 链表:每个 NUMA 节点维护 partial slab 列表,作为 CPU partial 的补充。
  • Full Slab:完全使用的 slab,不参与分配,等待释放后变为 partial。

2.3 kmalloc 与 kmem_cache_alloc

kmalloc() 使用预定义的通用缓存(kmalloc-8、kmalloc-16 到 kmalloc-8192 共 13 种大小)。如果对象大小不在预定义范围或需要特殊初始化,应使用 kmem_cache_create() 创建专用缓存:

// 创建专用缓存
struct kmem_cache *my_cache = kmem_cache_create("my_obj", sizeof(struct my_type), 0, SLAB_HWCACHE_ALIGN, NULL);
// 分配对象
struct my_type *obj = kmem_cache_alloc(my_cache, GFP_KERNEL);
// 释放
kmem_cache_free(my_cache, obj);

2.4 实战:Slab 内存泄漏排查

# 查看 slab 使用 Top 20
slabtop -o --sort=c | head -25

# 通过 /proc/slabinfo 查看具体缓存
cat /proc/slabinfo | awk 'NR==1 || * > 1000000' | column -t

如果发现某个 kmem_cache 的 total objects(第2列)持续增长且 active objects(第1列)极少,通常意味着该缓存的分配/释放不匹配,存在泄漏。使用 kmemleak 可以进一步定位未释放的调用栈:

echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak

三、页面回收与 Swap 机制

3.1 LRU 算法与两个链表

Linux 使用改进的双链 LRU 算法管理页面回收。每个 LRU 列表包含五种类型:LRU_INACTIVE_ANON、LRU_ACTIVE_ANON、LRU_INACTIVE_FILE、LRU_ACTIVE_FILE、LRRU_UNEVICTABLE。Active 列表中的页面被认为近期被访问过,Inactive 列表中的页面是回收候选。

页面访问时通过 PG_referenced 标志和 PTE 中的 Accessed 位决定升降级:第一次访问移到 active,第二次访问确认活跃状态;后台回收线程 kswapd 将 inactive 尾部的页面回收。

3.2 页面回收的触发条件与流程

页面回收在三种场景下触发:

  • Direct Reclaim(直接回收):进程分配内存时发现空闲低于 min_wmark,同步阻塞在回收路径上。这会导致性能抖动。
  • kswapd 后台回收:当空闲内存低于 low_wmark 时唤醒 kswapd,积极回收直到达到 high_wmark。
  • OOM Killer:当 kswapd 和 direct reclaim 都无法释放足够内存时触发。

3.3 回收参数调优

三个水位的计算公式为:min = min_free_kbyteslow = min * 5/4high = min * 3/2。调优关键参数:

参数说明建议值
vm.min_free_kbytes最小保留内存,防止 direct reclaim物理内存的 3%~5%,不少于 65536KB
vm.swappinessSwap 倾向(0-100)数据库:1~10;桌面:60;默认 60
vm.dirty_ratio脏页占系统内存%时强制刷盘高性能存储:40;SSD:20
vm.dirty_background_ratio后台 pdflush 启动阈值1/2 of dirty_ratio
vm.zone_reclaim_modeNUMA 节点本地回收NUMA 计算密集:1;通用:0

四、OOM Killer 深度剖析

4.1 选择算法

当系统内存耗尽且回收失败时,OOM Killer 通过 oom_badness() 函数为每个进程打分(0-1000),分数越高越可能被 kill:

  • 基础分数:进程占用的物理内存(RSS + Swap + Page Table + mmap)占总内存的比例乘以 1000。
  • 调整因子:根进程(-75%)、oom_score_adj 直接设置、运行时间长的进程减分、nice 值高的进程加分。
  • 核心豁免:init(PID 1)、内核线程、OOM_SCORE_ADJ_MIN(-1000)的进程不会被 kill。

4.2 cgroup OOM vs 系统 OOM

在容器化场景中,每个 cgroup 有自己的内存上限。当 cgroup 内存超限时触发 cgroup OOM,只 kill 该 cgroup 内的进程,不影响整个系统。systemd 和 Kubernetes 的 OOMKilled 事件就是基于此机制。

4.3 实战:OOM 诊断与防护

# 查看各进程的 OOM 评分
ps -eo pid,comm,oom_score,oom_score_adj --sort=-oom_score | head

# 保护关键进程(如数据库主进程)
echo -1000 > /proc//oom_score_adj

# 查看 OOM 事件历史
dmesg | grep -i "out of memory"
journalctl -k | grep -i "oom\|killed process"

# cgroup OOM 事件监控
cat /sys/fs/cgroup/memory/memory.oom_control
echo 1 > /sys/fs/cgroup/memory/memory.oom_control # 禁用自动 OOM(需手动处理)

4.4 生产环境最佳实践

  • 为数据库和缓存服务进程设置 oom_score_adj = -1000 或直接通过 systemd OOMPolicy=continue 配置。
  • 启用 cgroup 内存限制 memory.limit_in_bytesmemory.soft_limit_in_bytes,避免单服务耗尽系统内存。
  • 监控 oom_kill 指标(/proc/vmstat 中的 oom_kill 计数器),Prometheus 告警阈值设为 >0。

五、HugePages 与透明大页

5.1 为什么需要大页

标准 x86_64 页大小 4KB,TLB 通常只有 64-128 个条目。访问 1GB 内存需要约 26万 次页表查找,其中绝大部分因 TLB miss 需要多步页表遍历。2MB HugePage 将减少 512 倍页表项,大幅降低 TLB miss 率。对于数据库(PostgreSQL、Redis)和科学计算场景,性能提升可达 10%~30%。

5.2 静态 HugePages

静态 HugePages 需要在系统启动时预留,分配后资源被锁定,普通进程不可使用,但确保了大页不会被碎片化影响。配置方式:

# 计算所需 2MB 大页数:内存(GB) / 2 * 0.8(保留 80% 给大页)
echo 4096 > /proc/sys/vm/nr_hugepages
# 或 sysctl 持久化
echo "vm.nr_hugepages = 4096" >> /etc/sysctl.d/99-hugepages.conf

# PostgreSQL 配置
# postgresql.conf
huge_pages = on # 启用大页
shared_buffers = 16GB # 应与 HugePages 内存匹配

5.3 透明大页(Transparent HugePages)

THP 是内核通过 khugepaged 后台线程自动将连续 4KB 页合并为 2MB 大页的机制,无需应用修改。但对数据库场景,khugepaged 的频繁合并/拆分操作反而造成延迟抖动。MongoDB、PostgreSQL、Redis 官方文档均建议关闭 THP:

# 立即关闭
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

# 持久化(grub 参数)
# GRUB_CMDLINE_LINUX="transparent_hugepage=never"
grub2-mkconfig -o /boot/grub2/grub.cfg

5.4 监控 HugePages 使用

grep -i huge /proc/meminfo
# HugePages_Total: 4096
# HugePages_Free: 1024
# HugePages_Rsvd: 200
# Hugepagesize: 2048 kB

持续关注 HugePages_Free,若接近 0 会导致分配失败并 fallback 到普通页。建议设置 vm.nr_hugepages 时留 10%~15% 冗余。

六、KSM 内存去重(Kernel Samepage Merging)

6.1 工作原理

KSM 是内核的内存去重服务,通过 ksmd 后台线程扫描页面内容,找到相同内容的页面并合并为一个只读页面(Copy-on-Write),在写入时再复制。KSM 特别适用于运行大量相似虚拟机/容器的场景,可节省 30%~60% 内存。

6.2 KSM 配置与调优

# 启用 KSM
echo 1 > /sys/kernel/mm/ksm/run

# 每秒扫描页数(越大越积极,CPU 消耗越高)
echo 100 > /sys/kernel/mm/ksm/pages_to_scan

# 扫描间隔(毫秒)
echo 200 > /sys/kernel/mm/ksm/sleep_millisecs

# 查看节省的内存
grep -E "pages_shared|pages_sharing|pages_unshared|pages_volatile" /sys/kernel/mm/ksm/

pages_sharing 表示当前被共享的页面数,pages_shared 表示已被合并的唯一页面数。节省的内存约为 (pages_sharing - pages_shared) * 4KB

6.3 应用场景与权衡

  • 适合场景:VDI 桌面虚拟化、KVM 相同 OS 容器、大量相同基础镜像的 Docker 容器。
  • 不适合场景:容器内容差异大(去重率低)、CPU 敏感型延迟应用(ksmd 消耗 CPU)。
  • 安全考虑:KSM 可能被用于侧信道攻击(如通过计时差异推测其他 VM 的内容),高安全环境建议关闭。

七、NUMA 内存策略

7.1 NUMA 架构简介

NUMA(Non-Uniform Memory Access)架构将 CPU 和内存分为多个节点,每个节点内的内存访问速度最快(本地访问),跨节点访问需要通过 QPI/UPI 总线,延迟高 1.5~3 倍。现代多路服务器几乎都是 NUMA 架构。

7.2 NUMA 内存分配策略

内核提供四种 NUMA 内存策略:

  • Default(默认):在请求进程运行的 CPU 所在节点的本地分配。
  • Bind:强制从指定节点集合分配,若节点内存不足则触发 OOM。
  • Preferred:优先从指定节点分配,不足时 fallback 到其他节点。
  • Interleaved:在所有节点间轮询分配,均衡负载。

7.3 进程 NUMA 绑定实战

# 查看 NUMA 拓扑
numactl --hardware
numastat -m

# 绑定进程到 NUMA node 0 的 CPU 和内存
numactl --cpunodebind=0 --membind=0 ./my_db_server

# 查看进程的 NUMA 策略
numactl -p

# 查看进程各节点的内存使用
numastat

7.4 数据库 NUMA 调优

  • PostgreSQL:设置 shared_buffers 不超过单节点内存的 25%,确保主线程和 worker 在同一节点。numa=on 时 PG 会交错分配 shared buffers。
  • Redis:单实例建议绑定单节点(numactl --membind=0);避免跨节点访问导致 30% 性能损失。
  • InfluxDB / ClickHouse:OLAP 查询可以跨节点并行,但 memtable 和 write buffer 尽量本地化。
  • 内核参数numa_balancing 内核特性自动迁移页面到访问 CPU 所在节点,数据库场景建议关闭(echo 0 > /proc/sys/kernel/numa_balancing)。

八、生产环境监控与排错综合指南

8.1 关键监控指标

指标来源告警阈值
内存使用率/proc/meminfo> 85% 持续 5 分钟
可用内存MemAvailable< 总内存 10%
页扫描率pgscan_kswapd / pgscan_directdirect > 100/s 持续 2 分钟
OOM Kill 次数vmstat.oom_kill增量 > 0
HugePages 使用率HugePages_Free< 总页数 5%
NUMA 本地访问率numastat< 80>
Slab 占用/proc/meminfo Slab> 总内存 30%

8.2 高效排错流程图

系统响应变慢 / OOM → 查看 dmesg | grep -i 'oom\|kill' → 如果是 OOM:检查 dmesg 中的 OOM 日志确认被 kill 进程 → 检查 cgroup 内存限制 → 检查 vmstat 1 确认 si/so(swap 交换) → 检查 top RSS 占用 → 如果是 Slab 泄漏:slabtop + kmemleak;如果是应用内存增长:valgrind--leak-check

8.3 sysctl 调优模板

# /etc/sysctl.d/99-memory-tuning.conf
vm.min_free_kbytes = 262144
vm.swappiness = 10
vm.dirty_ratio = 20
vm.dirty_background_ratio = 10
vm.overcommit_memory = 0
vm.overcommit_ratio = 80
vm.zone_reclaim_mode = 0
vm.numa_balancing = 0
kernel.panic_on_oom = 0
vm.panic_on_oom = 0

sysctl -p /etc/sysctl.d/99-memory-tuning.conf

总结

Linux 内核内存管理是一个精密的分层系统:伙伴系统管理物理页面,Slab/SLUB 服务小对象分配,LRU 算法驱动页面回收,HugePages 减少 TLB miss,KSM 实现页面去重,NUMA 策略优化多路架构下的访问延迟。理解这些机制不仅有助于诊断 OOM、内存泄漏和性能抖动,更能在数据库部署、容器调优和高性能计算场景中做出正确的参数配置决策。建议读者结合自身场景,逐步建立内存指标的常态化监控体系,在问题发生前完成预防性调优。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部