1. 内存管理全景:Linux内核内存管理架构总览

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分配器构建在伙伴系统之上。两者形成分层协作的关系:

  1. 伙伴系统:以2^order个连续物理页框为基本单位进行分配和回收。通过alloc_pages(gfp_mask, order)接口向上层提供服务。其最大优点是外部碎片可控,但最小分配粒度为4KB(order=0),对于几十字节的内核对象来说浪费严重。
  2. slab分配器:调用伙伴系统的kmem_getpages()获取连续页框,然后在内部实现对象缓存。每个slab cache管理一种固定大小的对象(如32B、64B、128B…),通过kmem_cache_alloc()和kmem_cache_free()接口提供快速分配。
  3. 通用缓存:kmalloc()函数底层依赖slab分配器,它使用一组预定义尺寸的通用cache(如kmalloc-32、kmalloc-64…),满足内核中大量"小内存"的随机分配需求。
常见误区: 不要认为kmalloc()直接调用伙伴系统。kmalloc()在大多数情况下走slab/slub路径,只有分配大内存(超过一个页框)时才会直接调用alloc_pages()。理解这一分层对性能调优至关重要。

1.4 内存管理全景架构图

下图展示了从用户态malloc到物理页框的完整调用链,以及伙伴系统与slab分配器的协作关系:

Linux 内存管理全景架构图 用户态进程 (User Space) malloc() / mmap() / brk() → glibc → 系统调用 系统调用层:sys_brk / sys_mmap / sys_mprotect 虚拟内存管理 (VMA + 页表) mm_struct → 红黑树管理VMA → 四级页表 → TLB 缺页异常 (do_page_fault) 按需调页 / 写时复制 / 交换换入 直接分配路径 (alloc_pages / kmalloc) GFP标志 → 选择zone → 伙伴系统 伙伴系统 (Buddy System) alloc_pages(order) → 2^order 连续页框 → 合并/分裂算法 slab / slub 分配器 (对象缓存层) kmem_cache_alloc() → 从slab获取对象 → 着色/每CPU缓存 物理页框 (struct page / mem_map) NUMA节点 → zone → 伙伴链表 → 页框回收

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使用情况,建立感性认识。