Linux内核内存管理深度实战:从Buddy System到SLAB分配器的完整剖析
引言
内存管理是Linux内核中最核心、最复杂的子系统之一。它不仅决定了系统能否高效利用有限的物理内存资源,更直接影响着系统的稳定性、性能和可扩展性。本文将从内核源码级别,深入剖析Linux内存管理的核心机制——Buddy System(伙伴系统)、SLAB/SLUB/SLOB分配器、缺页中断处理、反向映射(Reverse Mapping)以及内存回收(kswapd)等关键技术。
1. 物理内存模型:NUMA与Zone
现代服务器普遍采用NUMA(Non-Uniform Memory Access)架构,CPU访问本地节点内存远快于远程节点。Linux内核通过NODE_DATA宏为每个NUMA节点维护独立的pglist_data结构。
物理内存按Zone划分:
- ZONE_DMA: 0-16MB,用于早期DMA设备
- ZONE_DMA32: 16MB-4GB,32位DMA设备
- ZONE_NORMAL: 直接映射区(896MB),线性映射到内核空间
- ZONE_MOVABLE: 可移动区,用于内存热插拔
- ZONE_HIGHMEM: 高端内存(64位已不需要)
每个Zone维护自己的水位线(min/low/high),当可用内存低于low水位时,kswapd后台回收启动;低于min水位时,分配器进入直接回收(Direct Reclaim),阻塞调用者。
2. Buddy System(伙伴系统)——页框管理基石
Buddy System是物理页框(struct page)分配与回收的核心算法。它将空闲页按大小分组为11个链表(order 0-10),分别管理1, 2, 4, ... 1024个连续页框。
// 核心结构: free_area
struct free_area {
struct free_list free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
// 分配 order-n 页框时:
// 1. 检查 free_list[n] 是否有空闲块,有直接取
// 2. 无则向上查找更大 order,找到后拆分(buddy split)
// 3. 回收时检查 buddy 是否空闲,空闲则合并(coalesce)
伙伴判断公式:buddy_pfn = page_pfn XOR (1 << order),即第order位翻转即得伙伴页框号。
碎片避免策略:通过page迁移类型(MIGRATE_UNMOVABLE/MIGRATE_MOVABLE/MIGRATE_RECLAIMABLE/MIGRATE_ISOLATE/MIGRATE_PCP)将不同特性的页面隔离到不同链表,减少碎片化。此外还有"压缩"(compaction)机制移动不可腾挪页面。
3. SLAB/SLUB分配器——内核对象缓存
Buddy System以页(4KB/2MB)为单位分配,但内核中大量分配远小于4KB的对象(如task_struct、dentry、inode)。SLAB分配器在Buddy System之上构建对象缓存,减少内部碎片并提升分配速度。
三类分配器
- SLAB: 原始实现,缓存队列管理活跃/部分/空闲slab
- SLUB(默认):简化队列、元数据嵌入page结构、更好的调试
- SLOB: 极简版本,用于嵌入式(<32MB内存的系统)
// kmem_cache: 一种对象类型的缓存描述符
struct kmem_cache {
struct kmem_cache_cpu __percpu *cpu_slab; // 每CPU快速分配
struct kmem_cache_node *node[MAX_NUMNODES]; // 每节点后备
unsigned int object_size; // 对象大小
unsigned int offset; // 空闲链表指针偏移
struct kmem_cache_order_objects oo; // 每slab页数
...
};
// 分配路径: kmem_cache_alloc()
// 1. 检查 cpu_slab->freelist 有直接拿(fastpath)
// 2. 无则从 node->partial 取slab填充(slowpath)
// 3. 也无则向 Buddy System 请求新slab
SLOB分配器使用简单fit算法(first-fit/best-fit),适合内存极少的系统。现代发行版默认使用SLUB。
4. 缺页中断与虚拟内存映射
Linux使用四级页表(x86_64):PGD→P4D→PUD→PMD→PTE。当CPU访问未映射地址时触发Page Fault(#PF),进入缺页处理流程do_page_fault→handle_mm_fault。
三种缺页场景:
- 缺页(Demand Paging): 首次访问映射区域,分配物理页
- COW(写时复制): fork后父子共享只读页,写时触发复制
- 交换页(Swap-in): 页面被换出到磁盘,访问时重新调入
反向映射(Reverse Mapping)允许内核从物理页反查所有映射它的VMA,是页面回收(page_referenced→rmap_walk)的关键基础设施。匿名页通过anon_vma链表映射,文件页通过address_space的radix树映射。
5. 内存回收与OOM Killer
当系统内存紧张时,回收路径:
kswapd (后台异步) → Direct Reclaim (同步阻塞) → OOM Killer (最后手段)
↓ ↓ ↓
水位线触发 分配者等待 杀进程救命
扫描不活跃LRU 同步扫描LRU 选oom_score最大者
写回脏页 丢弃干净页
LRU算法使用两个列表(active/inactive),页面访问后升入active,久未访问降入inactive,回收时从inactive开始。swap机制允许将不活跃匿名页写入磁盘交换空间。
OOM Killer通过oom_badness()函数计算每个进程的OOM score(基于内存占用、运行时间、nice值等),选择得分最高的进程杀死。/proc/[pid]/oom_score_adj允许管理员调整。
6. 大页(Huge Pages)与透明大页(THP)
x86_64默认4KB页,但TLB缓存有限。使用2MB/1GB大页可显著减少TLB miss:
- 静态大页: 启动时预留,hugetlbfs文件系统访问
- 透明大页(THP): 内核自动合并普通页为2MB大页(khpaged守护进程)
- 1GB HugePD: 超大页,适用于大型数据库和HPC场景
数据库(如Oracle/PostgreSQL)通常显式使用静态大页(vm.nr_hugepages)获得稳定低延迟。THP在虚拟化环境中可能引起性能抖动,某些场景建议禁用(/sys/kernel/mm/transparent_hugepage/enabled = never)。
7. 调优实战与监控
关键sysctl参数:
- vm.swappiness (0-100): 控制使用swap倾向,数据库通常设为1-10
- vm.dirty_ratio/dirty_background_ratio: 脏页比例阈值,触发回写
- vm.min_free_kbytes: min水位线绝对值,不能被Buddy分配
- vm.overcommit_memory: 是否允许内存超分配(0=启发式 1=总是允许 2=不允许)
- vm.zone_reclaim_mode: NUMA节点是否优先回收本地内存
监控工具链:
- /proc/meminfo: 系统内存全景
- /proc/buddyinfo: Buddy System各order空闲块数
- /proc/slabinfo: SLAB缓存状态
- vmstat / sar -B: 页面活动统计
- perf record -e page-faults: 缺页中断分析
- numastat: NUMA分布统计
8. 前沿趋势
Linux内存管理仍在持续演进:
- MGLRU(Multi-Gen LRU): Linux 6.1引入多代LRU,显著提升回收效率
- Damon(Data Access Monitor): 硬件级访问监控驱动内存调优
- : 新型分解架构内存池化场景
- Folio: 2021年引入的超页(superpage)抽象,统一管理复合页
- cgroup v2 memory controller: 容器化场景细粒度内存控制
总结
Linux内存管理是一个层次化体系:Buddy System管理物理页框→SLAB/SLUB管理内核对象→虚拟内存(VMA+页表)抽象进程地址空间→kswapd/OOM管理回收。理解这些机制,是性能调优、内核开发、故障排查的必备基础。建议结合QEMU中编译自定义内核、源码阅读(mm/page_alloc.c, mm/slab.c, mm/memory.c)和bpftrace实操深入掌握。

发表评论 取消回复