Linux内核内存管理深度实战:从伙伴系统到SLUB分配器

引言

Linux内核的内存管理是操作系统中最复杂且最核心的子系统之一。它不仅负责物理内存的分配与回收,还涉及虚拟内存映射、页面回收、内存碎片化控制等关键机制。理解Linux内存管理,是深入系统底层、优化高性能应用的必备技能。

本文将从内核源码层面,深入剖析Linux内存管理的核心机制,包括伙伴系统(Buddy System)、SLAB/SLUB分配器、虚拟内存区域(VMA)、页面回收(Page Reclaim)以及内存控制组(memcg)等关键组件。

一、内存管理整体架构

Linux内存管理采用分层架构,从上到下依次为:

  • 用户空间层:malloc/mmap等系统调用接口
  • 内核虚拟内存层:VMA管理、页表映射、缺页处理
  • 物理内存分配层:伙伴系统 + SLUB分配器
  • 硬件层:MMU、TLB、多级页表

在NUMA架构中,内存进一步分为多个节点(pg_data_t),每个节点又划分为不同的内存区域(ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM),以满足不同硬件设备的DMA需求。

二、伙伴系统(Buddy System)

2.1 核心设计思想

伙伴系统是Linux物理页框分配的基础算法,由Knowlton于1965年提出。其核心思想是将内存划分为2的幂次大小的块组,当需要分配N页时:

  1. 从对应阶(order)的空闲链表中查找
  2. 若找到,直接分配
  3. 若未找到,从更高阶链表中分裂,将剩余部分挂入低阶链表
  4. 释放时检查"伙伴"是否空闲,若空闲则合并

2.2 源码实现分析

在Linux内核中,伙伴系统的核心数据结构是free_area数组:

struct free_area {
    struct free_list free_list[MIGRATE_TYPES];
    unsigned long    nr_free;
};

每个内存节点(pg_data_t)包含一个free_area数组,最大阶为MAX_ORDER(通常是11),支持最大4MB(2^11 * 4KB)的连续物理内存分配。

关键函数路径:

  • alloc_pages() → __alloc_pages_nodemask() → get_page_from_freelist() → rmqueue()
  • __free_pages() → __free_one_page() → buffered_rmqueue()

2.3 迁移类型与碎片控制

Linux引入了页面迁移类型(migrate types)来解决碎片化问题:

  • MOVABLE:用户空间页面,可通过页面迁移移动
  • RECLAIMABLE:可回收页面(如缓存页)
  • UNMOVABLE:不可移动页面(如内核数据)

通过将不同迁移类型的页面分组,内核可以有效减少内存碎片,提高大块内存分配成功率。

三、SLUB分配器详解

3.1 SLUB的设计目标

SLUB(Unqueued Slab Allocator)是Linux默认的slab分配器,相比SLAB:

  • 移除了每CPU/每节点的复杂队列结构
  • 简化了调试和错误处理机制
  • 在现代多核系统上性能更优

3.2 Slab分配器核心概念

Slab分配器在内核伙伴系统之上构建,用于分配小对象内核内存:

  • 缓存(kmem_cache):特定大小/类型的对象缓存
  • Slab:一个或多个连续物理页,被划分为固定大小的槽位
  • 对象(Object):slab中的最小分配单元

3.3 三层分配架构

SLUB采用三层架构实现高效内存分配:

  1. CPU Partial List:每个CPU的部分空闲slab,无锁快速路径
  2. Node Partial List:NUMA节点的部分空闲slab
  3. Full Slab:已完全分配的slab

分配优先级为:CPU partial → Node partial → 伙伴系统分配新页。这种分层设计在多核环境下显著减少了锁竞争。

3.4 kmem_cache_alloc 核心流程

kmem_cache_alloc()
  → slab_alloc()
    → ___slab_alloc()        // 快速路径:CPU partial
    → __slab_alloc()         // 慢速路径
      → new_slab()           // 从伙伴系统获取新页
        → allocate_slab()
          → alloc_pages()

四、虚拟内存与页表管理

4.1 四级/五级页表结构

Linux在x86_64架构上使用四级页表(PGD→PUD→PMD→PTE),五级页表(PGD→P4D→PUD→PMD→PTE)用于支持更大地址空间:

  • PGD(Page Global Directory):顶级页表,每个进程一个
  • PUD(Page Upper Directory):在x86_64上通常折叠为PGD
  • PMD(Page Middle Directory)
  • PTE(Page Table Entry):存储物理页框号和访问权限

4.2 缺页异常处理

当CPU访问虚拟地址触发Page Fault时,内核需要处理两种情况:

  • Demand Paging:首次访问匿名页面,分配零页
  • Page Cache Miss:文件映射页面从磁盘读取

handle_mm_fault()是缺页处理的核心入口,根据VMA类型和页表状态选择具体的处理函数。

五、页面回收与交换机制

5.1 LRU算法与双链策略

Linux使用近似LRU算法来管理可回收页面。内核维护两个LRU链表:

  • Active List:活跃页面,近期被访问过
  • Inactive List:非活跃页面,候选回收

页面在两个链表之间移动:两次访问间隔内被访问则提升为Active,长时间未访问则降级为Inactive并被回收。

5.2 kswapd与直接回收

页面回收由kswapd守护进程和直接回收两种方式执行:

  • kswapd:水位线触发,异步后台回收
  • Direct Reclaim:分配路径上内存不足时同步回收,影响延迟

5.3 Swap与zRAM

Linux支持传统磁盘交换和压缩内存交换:

  • Swap Partition/File:将匿名页写入磁盘
  • zRAM:内存中压缩匿名页,避免磁盘I/O

六、内存控制组(memcg)

内存控制组允许按组限制、记账内存使用,是容器技术的基础:

  • memory.limit_in_bytes:硬限制,超出触发OOM
  • memory.soft_limit_in_bytes:软限制,内存紧张时生效
  • memory.kmem.limit_in_bytes:内核内存限制

v2版本引入了更精细的min/max/high/low分层控制,更好地支持复杂工作负载。

七、性能调优实践

7.1 减少直接回收

直接回收会阻塞应用程序,增加延迟。调优方法:

  • 设置适当的min_free_kbytes(低于低水位时提前唤醒kswapd)
  • 使用memory cgroup预留足够内存
  • 禁用透明大页(THP)或改为madvise模式

7.2 SLUB分配器参数

cat /proc/slabinfo
sysctl -a | grep slub

7.3 内存碎片化监控

使用/proc/buddyinfo查看各阶空闲页面数量。当高阶连续物理内存不足时,可尝试触发内存规整:

echo 1 > /proc/sys/vm/compact_memory

八、总结

Linux内核内存管理是一个精心设计的多层系统:伙伴系统管理物理页框,SLUB分配器优化小对象分配,页面回收机制平衡内存压力。理解这些底层机制,对于系统性能调优、内核驱动开发以及高性能应用设计都至关重要。

在生产环境中,建议通过监控/proc/meminfo、/proc/vmstat、slabinfo等接口持续观察内存行为,结合具体业务场景进行针对性调优。


本文基于Linux 6.x内核源码分析,涉及的关键代码路径位于mm/目录(mm/page_alloc.c、mm/slub.c、mm/vmscan.c、mm/memory.c等)。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
0.355678s