一、物理内存基石:Buddy System 分配器
1.1 核心思想
Linux 内核以页面(Page,通常 4KB)为最小单位管理物理内存。Buddy System(伙伴系统)将所有空闲页面组织为 11 个链表,分别管理 1、2、4、8、...、1024 个连续页面块(即 order 0~10)。
// mm/page_alloc.c 核心数据结构
struct free_area {
struct free_list free_list[MIGRATE_TYPES];
unsigned long nr_free;
};当请求 2 个页面但 order-1 链表为空时,内核从 order-2 链表中取出一个 4 页块,拆分一半分配,另一半留在 order-1 链表——这便是“伙伴”名称的由来。
1.2 迁移类型(Migrate Types)
为对抗物理碎片化,Buddy System 按页面可迁移性分类:
MIGRATE_UNMOVABLE:内核数据结构、页表项、不可移动的 slab 页面。
MIGRATE_MOVABLE:用户空间页面、文件缓存、可重分配的 slab 缓存。
MIGRATE_RECLAIMABLE:文件系统元数据、可回收的 inode/dentry 缓存。
这种分离确保了在需要整合连续物理内存时,只需移动 MOVABLE 类型页面。
1.3 Per-CPU 热页缓存(PCP)
多核场景下 Buddy System 的全局锁成为瓶颈,因此每个 CPU 维护独立的热页缓存队列:
// include/linux/mmzone.h
struct per_cpu_pageset {
struct per_cpu_pages pcp; // 冷热页面_batch管理
s8 expire;
u16 count; // 单CPU缓存页面数
} ____cacheline_aligned_in_smp;单 CPU 分配优先走 PCP 链表,低于 high watermark 时从 Buddy System 批量补充,高于 max 时归还。这对单线程顺序分配场景极为高效。
二、内核对象分配:Slab 分配器家族
2.1 Slab 的设计哲学
Buddy System 最小分配单位为 4KB 页,但内核频繁创建销毁的对象往往只有几百字节(task_struct ~1.7KB、inode ~600B)。于是 Slab 分配器作为建立在 Buddy System 之上的对象缓存层出现。
Slab 将一页(或连续多页)划分为若干等长 slot,每个 slot 存放一个内核对象,通过 free list 跟踪空闲位置。
2.2 Slab、SLOB、SLUB 三代演化
Linux 内核经历了三代 Slab 实现:
Slab(1994 Linux 1.0 继承 UV):每个 slab 维护本地 free list,对象分空闲/部分/满三种状态。硬件缓存友好但管理开销大。
SLOB(Simple List of Blocks):为嵌入式设备设计,用 first-fit 算法在空闲页面中线性搜索。代码约 1500 行但碎片严重,仅用于 CONFIG_SLOB 编译选项。
SLUB(Unqueued Slab,2.6.23 成为默认):现今 Linux 默认分配器。每个 CPU 维护自己的 partial 页面链表,消除了 Slab 中的共享锁。对象释放时直接放回原 slab 的 free list,利用硬件缓存局部性。
2.3 Slab 的 CPU 本地缓存机制
// mm/slub.c 简化结构
struct kmem_cache {
struct kmem_cache_cpu *cpu_slab; // per-cpu 本地缓存
struct kmem_cache_node *node[MAX_NUMNODES]; // per-node partial
unsigned int object_size; // 对象实际大小
unsigned int size; // 含元数据的对象大小
unsigned int offset; // free pointer 偏移
struct kmem_cache_order_objects oo; // 最佳order
}SLUB 的 CPU 本地缓存中,free list 通过嵌入指针实现——空闲 slot 内存直接存储下一个空闲 slot 地址。对象分配本质是从 per-cpu page 上 pop 一个指针,O(1) 完成。
三、虚拟内存映射:页表与 VMA
3.1 四级页表架构
Linux 在 x86-64 上采用四级页表(PML4→PDPT→PD→PT),虚拟地址划分为:
47 39 30 21 12 0
| PML4 index (9bit) | PDPT(9) | PD(9) | PT(9) | offset(12) |
|————————— 39bit —————————————|每级页表 512 项(4KB 页 / 8B 项),覆盖 256TB 地址空间。当应用使用超过 48bit 地址时(Intel 5-level page table / LA57),扩展为五级页表覆盖 128PB。
3.2 TLB 与 ASID/PCID
地址转换的性能关键在 TLB(Translation Lookaside Buffer),它是页表的硬件缓存。TLB miss 需四次内存 walk,代价极高。
内核通过以下技术减小 TLB 刷新频率:
- ASID/PCID:TLB 项标记进程标识,进程切换无需 flush TLB
- Lazy TLB:内核线程借用上一个进程的页表,避免刷新
- Huge Pages / THP:使用 2MB/1GB 大页减小 TLB miss(4KB 页需 512K 项覆盖 256GB,2MB 页仅需 131K 项)
3.3 VMA(Virtual Memory Area)
用户进程的虚拟地址空间由红黑树组织的 VMA 区间表示,每个 VMA 描述一段连续区域的权限、后备存储和 flags:
// include/linux/mm_types.h
struct vm_area_struct {
unsigned long vm_start, vm_end; // 区间范围
struct mm_struct *vm_mm; // 所属 mm
pgprot_t vm_page_prot; // 访问权限
unsigned long vm_flags; // READ/WRITE/EXEC/SHARED
const struct vm_operations_struct *vm_ops; // fault/ioctl handler
struct file *vm_file; // 映射文件(若有)
unsigned long vm_pgoff; // 文件偏移(页对齐)
};四、页面回收与 OOM 决策
4.1 LRU 算法与双链表
内核使用双链表 LRU 算法追踪页面活跃度:
Active List:近期访问过的页面
Inactive List:长时间未访问的候选回收页面
每次页面访问通过 PG_accessed 硬件标志(x86)或软件模拟将其从 inactive 提升到 active。回收时从 inactive 链表尾部逐出。匿名页面(匿名映射)在 swap 被逐出前会先进入 swap cache。
4.2 kswapd 与 Direct Reclaim
后台守护进程kswapd在每个 NUMA node 上运行,当空闲页面低于 low watermark 时异步唤醒,从 inactive 链表回收页面直到 high watermark。
当应用突发内存分配且 kswapd 来不及回收时,触发Direct Reclaim(直接回收)——分配路径同步进行页面回收。Direct Realloc 严重影响应用延迟,这也是“慢路径”分配(alloc_pages(GFP_KERNEL))的核心痛点。
4.3 OOM Killer 的评分逻辑
当所有回收手段耗尽仍无法满足分配请求时,触发 OOM Killer。它通过 oom_badness() 为每个进程评分:
// mm/oom_kill.c 简化逻辑
totalpages = total_ram + total_swap;
points = (get_mm_rss(mm) + get_mm_counter(mm, MM_SWAPENTS) + mm->nr_ptes) / totalpoints *= 1000;
oom_score = points + (oom_score_adj * totalpages / 1000);评分因子:RSS + swap 占用 + PTE 页表占比(比值基准),加上 /proc/<pid>/oom_score_adj 手动调整。内核线程(PF_NO_SETUID 标记)和 init 进程的 oom_score_adj 设为 -1000 免疫。
4.4 memcg OOM vs 全局 OOM
当限制在 cgroup v1 memory.limit_in_bytes 或 v2 memory.max 内时,优先触发 memcg OOM——仅杀死该 cgroup 内的进程,不影响其他 cgroup。memcg OOM 评分仅基于组内 RSS,不参考全局内存压力。
五、内存碎片整理与 THP
5.1 Anti-Fragmentation 机制
Buddy System 的迁移类型分类已对抗外部碎片。内核还提供 memory compaction 主动整理碎片:
// mm/page_alloc.c → compact_zone()
// 1. 扫描 zone,分离 movable 页面到临时链表
// 2. 调用 page migration 将 movable 页面搬离
// 3. 原区域合并为更大连续块压实由 /proc/sys/vm/compact_memory 触发或由 khugepaged(针对 THP)按需发起。
5.2 Transparent Huge Pages(THP)
THP 由 khugepaged 后台守护进程合并 4KB 基础页为 2MB 大页:
// 查看 THP 状态
cat /sys/kernel/mm/transparent_hugepage/enabled # always/madvise/neverTHP 适合内存数据库(Redis/PostgreSQL)和大内存应用,但其延迟 spike(合页时的 stop-the-world 停顿)使其不适合延迟敏感型服务(如交易系统)。生产建议:延迟敏感型服务设为 madvise,仅对明确调用 MADV_HUGEPAGE 的映射启用。
5.3 KSM(Kernel Samepage Merging)
KSM 扫描内存中内容相同的页面合并为一例(COW 共享),减少冗余内存占用。适用于 KVM 虚拟化环境(多个 VM 运行相同 OS 内核)。
// /sys/kernel/mm/ksm/ 控制接口
pages_to_scan // 每次扫描页数
sleep_millisecs // 扫描间隔
pages_shared // 当前共享页数量KSM 的扫描开销随内存增大而增长,大规模部署时 CPU 占用可达 3%~10%,需权衡收益。
六、生产环境调优实战
6.1 关键 sysctl 参数
# 减少 swap 倾向(默认60,0=积极swap)
vm.swappiness = 10
# 脏页刷新策略(CentOS7+默认)
vm.dirty_ratio = 40 # 单文件脏页占总内存%时同步写
vm.dirty_background_ratio = 10 # pdflush/flush 后台写回阈值
# 内存分配水位(per-zone 的 min/low/high)
vm.min_free_kbytes = 262144 # 256MB emergency 保留
# OOM 行为调整
vm.panic_on_oom = 0 # OOM时触发 kernel panic(配合kdump调试)
vm.overcommit_memory = 2 # 严格 overcommit,拒绝超过 CommitLimit 的分配6.2 监控与诊断工具
vmstat -SM 1:查看 si/so(swap in/out)、free、buff、cache 实时变化
slabtop:观察 slab 缓存实时分配(如 dentry/inode_cache 是否无限膨胀)
numastat -p <pid>:某进程在各 NUMA 节点的内存分布
bpftrace -e kswapd:...:追踪 kswapd 唤醒频率与回收效率
echo m > /proc/sysrq-trigger:dump 当前内存信息到 dmesg,包含各 zone 水位、OOM 评分快照
6.3 NUMA 亲和性优化
多槽服务器中本地节点内存访问延迟比跨槽低~30%。关键优化:
# 查看 NUMA 拓扑
numactl --hardware
# 绑定应用到本地节点
numactl --cpunodebind=0 --membind=0 /path/to/app
# 或 taskset CPU 亲和 + mbind 内存策略
numactl --physcpubind=0-31 --membind=0 ./app数据库和 Redis 强烈建议开启 numad 或使用 taskset + mbind,避免跨槽抖动引发 P99 毛刺。
七、性能调优实战案例
案例1:Redis P99 延迟抖动:诊断发现 khugepaged 周期性合页导致 200ms+ 停顿。修复:echo never > /sys/kernel/mm/transparent_hugepage/enabled,P99 稳定在 800μs。
案例2:容器化应用频繁 OOMKilled:limit=4GB 但应用峰值 RSS 达 4.3GB。修复:提高 limit 至 6GB + 配置 JVM -XX:MaxRAMPercentage=75 预留 off-heap 空间;同时设置 oom_score_adj=-500 保护关键 sidecar。
案例3:MySQL 查询偶发超时:numastat 显示 80% 内存在 node1 但 mysqld 绑定在 node0。修复后延迟下降 40%。
八、总结
Linux 内核内存管理是一个精密的多层系统:物理分配器 Buddy System 管理连续页面,Slab 层(SLUB)优化小对象分配,VMA 和 TLB 支撑虚拟内存映射,LRU/kswapd/OOM Killer 构成回收与决策链路。理解这些机制及其参数接口,才能在生产环境中做出正确的内存调优决策。下次我们将进入文件系统与 VFS 层的深度实战分析。

发表评论 取消回复