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实操深入掌握。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部