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页时:
- 从对应阶(order)的空闲链表中查找
- 若找到,直接分配
- 若未找到,从更高阶链表中分裂,将剩余部分挂入低阶链表
- 释放时检查"伙伴"是否空闲,若空闲则合并
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采用三层架构实现高效内存分配:
- CPU Partial List:每个CPU的部分空闲slab,无锁快速路径
- Node Partial List:NUMA节点的部分空闲slab
- 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等)。

发表评论 取消回复