1. 内存管理全景
Linux内核的内存管理子系统是操作系统最核心、最复杂的模块之一。它需要同时满足性能、碎片控制、硬件抽象和多进程隔离四大目标。本章从全局视角出发,梳理物理内存、虚拟内存、伙伴系统与slab分配器之间的层级关系,为后续深入slab机制奠定基础。
1.1 内存管理分层架构
Linux内核将内存管理抽象为三个主要层次,每一层解决不同粒度与不同场景的分配需求:
| 层次 | 核心组件 | 分配粒度 | 主要职责 |
|---|---|---|---|
| 虚拟内存层 | MMU、页表、VMA、缺页异常 | 4KB 页(或大页) | 进程地址空间管理、按需调页、内存映射、交换 |
| 物理内存管理层 | 伙伴系统(Buddy System) | 2^order 个连续物理页 | 物理页框分配与回收、避免外部碎片 |
| 对象缓存层 | slab / slub / slob 分配器 | 字节级(小对象) | 内核对象缓存、减少内部碎片、提高缓存命中率 |
关键认知: 伙伴系统是物理内存的"批发商",slab分配器是"零售商"。slab从伙伴系统批量获取2^order个连续页框,然后将其切分为同尺寸的小对象,供内核频繁创建/销毁的数据结构(如task_struct、inode、dentry)使用。
1.2 物理内存与虚拟内存
物理内存是硬件提供的真实RAM,而虚拟内存是CPU的MMU为每个进程提供的独立、连续的地址空间。两者通过页表建立映射关系。
- 物理内存:由
struct page数组管理,每个物理页框对应一个page结构体。内核通过pg_data_t(NUMA节点)和zone(ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM)组织物理页。 - 虚拟内存:每个进程拥有独立的
mm_struct,描述代码段、数据段、堆、栈、mmap区域。虚拟地址通过多级页表(x86_64四级页表)转换为物理地址。 - 缺页异常:当进程访问未建立映射的虚拟地址时,CPU触发缺页异常,内核按需分配物理页并建立页表项。
性能要点: TLB(快表)缓存了最近使用的虚拟-物理地址转换。slab分配器通过对象复用和着色(coloring)技术,尽量让同一cache的对象落在同一物理页框内,从而减少TLB抖动。
1.3 伙伴系统与slab分配器的关系
伙伴系统(Buddy System)是Linux物理内存分配的核心算法,而slab分配器构建在伙伴系统之上。两者形成分层协作的关系:
- 伙伴系统:以2^order个连续物理页框为基本单位进行分配和回收。通过
alloc_pages(gfp_mask, order)接口向上层提供服务。其最大优点是外部碎片可控,但最小分配粒度为4KB(order=0),对于几十字节的内核对象来说浪费严重。 - slab分配器:调用伙伴系统的
kmem_getpages()获取连续页框,然后在内部实现对象缓存。每个slab cache管理一种固定大小的对象(如32B、64B、128B…),通过kmem_cache_alloc()和kmem_cache_free()接口提供快速分配。 - 通用缓存:
kmalloc()函数底层依赖slab分配器,它使用一组预定义尺寸的通用cache(如kmalloc-32、kmalloc-64…),满足内核中大量"小内存"的随机分配需求。
常见误区: 不要认为
kmalloc()直接调用伙伴系统。kmalloc()在大多数情况下走slab/slub路径,只有分配大内存(超过一个页框)时才会直接调用alloc_pages()。理解这一分层对性能调优至关重要。
1.4 内存管理全景架构图
下图展示了从用户态malloc到物理页框的完整调用链,以及伙伴系统与slab分配器的协作关系:
1.5 分配路径决策树
内核在分配内存时,根据请求大小和GFP标志选择不同的底层实现。以下决策树总结了关键路径:
内存分配请求 (kmalloc / kmem_cache_alloc / alloc_pages)
│
├─ 请求大小 >= (PAGE_SIZE << 1) 且 非连续标志?
│ └─ 是 → vmalloc() → 非连续页框映射
│
├─ 请求大小 >= PAGE_SIZE?
│ └─ 是 → alloc_pages() → 伙伴系统 → 物理连续页框
│
├─ 请求大小 < PAGE_SIZE 且 有专用cache?
│ └─ 是 → kmem_cache_alloc() → slab/slub分配器
│
└─ 请求大小 < PAGE_SIZE 且 无专用cache?
└─ kmalloc() → 通用slab cache (kmalloc-32/64/128...)
性能调优启示: 对于频繁分配/释放的内核数据结构(如网络socket、文件inode),应创建专用slab cache而非使用通用kmalloc。专用cache可以避免内部碎片,并利用slab的每CPU缓存减少锁竞争。
1.6 关键数据结构关系表
下表总结了内存管理各层次的核心数据结构及其关联:
| 层次 | 核心数据结构 | 关键字段/方法 | 关联关系 |
|---|---|---|---|
| 虚拟内存 | mm_struct |
pgd, mmap, mm_rb, map_count | 每个进程唯一,指向页全局目录 |
| 虚拟内存 | vm_area_struct |
vm_start, vm_end, vm_flags, vm_ops | 红黑树节点,描述连续虚拟区间 |
| 物理内存 | pg_data_t |
node_zones, node_zonelists, nr_zones | NUMA节点描述符 |
| 物理内存 | struct zone |
free_area, watermark, lowmem_reserve | 包含11个free_area链表 |
| 伙伴系统 | free_area |
free_list, nr_free | 每个order一个,双向链表 |
| slab分配器 | kmem_cache |
object_size, offset, cpu_cache, node | 管理同尺寸对象池 |
| slab分配器 | struct slab |
s_mem, freelist, inuse, active | 一个或多个连续页框 |
1.7 本章小结
Linux内存管理采用分层抽象的设计哲学:虚拟内存层提供进程隔离和按需分配,伙伴系统解决物理页框的连续性与外部碎片问题,slab分配器在内核对象级别实现高效缓存与复用。理解这三层之间的调用关系和数据流,是后续深入分析slab着色、每CPU缓存、NUMA感知等高级特性的前提。
实战思考: 在下一章中,我们将深入slab分配器的内部实现,重点分析
struct kmem_cache的布局、slab着色算法以及kmem_cache_alloc()的完整执行路径。建议读者先通过/proc/slabinfo观察当前系统的slab使用情况,建立感性认识。