1. 内存管理全景:物理内存与虚拟内存、伙伴系统与slab/vmalloc的定位

内存管理是操作系统内核最核心的子系统之一。理解物理内存与虚拟内存的映射关系、伙伴系统(Buddy System)的页级分配机制,以及 slab 与 vmalloc 在内核中的角色,是掌握内存分配优化的前提。本章从全景视角梳理这些概念,为后续的实战调优打下坚实基础。

1.1 物理内存与虚拟内存:两大抽象层次

现代操作系统通过虚拟内存(Virtual Memory)为每个进程提供独立的地址空间,而物理内存(Physical Memory)则是硬件实际可寻址的存储介质。内核必须在这两层之间建立高效、安全的映射。

特性 物理内存 虚拟内存
定义 硬件 RAM 芯片的实际存储单元 CPU 通过 MMU 映射给进程的地址空间
地址空间 受限于硬件容量(如 4GB、16GB) 通常远大于物理内存(如 48 位地址空间)
分配粒度 页(通常 4KB) 页(4KB)或更大的段
管理主体 伙伴系统 + 页分配器 MMU + 页表 + 内核内存管理器
关键挑战 碎片化、连续性、NUMA 亲和性 TLB 抖动、缺页异常、映射开销
核心认知:物理内存是“土地”,虚拟内存是“地图”。内核既要管理好土地(分配/回收物理页),也要维护好地图(页表/映射关系)。slab 和 vmalloc 分别在这两层上提供不同粒度的分配服务。

1.2 伙伴系统(Buddy System)与页分配器

伙伴系统是内核管理物理页帧(Page Frame)的核心算法。它将空闲页按 2 的幂次方(order)组织成链表,分配时从最小满足需求的 order 链表中取出一块,若该链表为空则向更高 order 请求并分裂;释放时检查其“伙伴”是否空闲,若是则合并回更高 order。

关键数据结构:每个内存域(zone)维护一个 free_area[11] 数组,对应 order 0(4KB)到 order 10(4MB)。
分配函数:alloc_pages(gfp_mask, order) 返回 struct page *。
释放函数:__free_pages(page, order)。
伙伴系统分配与合并流程 order 2 (16KB) order 2 (16KB) order 3 (32KB) order 3 (32KB) 分配 order 1 (8KB) order 1 (已分配) order 1 (空闲) 释放 order 1,合并伙伴 合并后 order 2 (16KB) 空闲
常见陷阱:伙伴系统虽然能有效减少外部碎片,但内部碎片(分配 3KB 实际占用 4KB 页)无法避免。此外,频繁的 order 0 分配与释放会导致伙伴合并延迟,进而影响大块连续内存的可用性。这是引入 slab 分配器的直接原因之一。

1.3 slab 分配器:内核对象缓存的基石

slab 分配器(以及其变体 slub、slob)专门用于管理内核中频繁创建和销毁的小对象(如 task_struct、inode、dentry)。它基于伙伴系统提供的物理页,通过缓存预分配的对象来减少分配开销,并利用着色(coloring)技术优化 CPU 缓存利用率。

特性 伙伴系统 slab 分配器
分配粒度 页(4KB ~ 4MB) 字节(8B ~ 8KB)
适用场景 大块连续内存、DMA 缓冲区 内核对象、小数据结构
碎片类型 外部碎片(可合并缓解) 内部碎片(通过对象大小对齐控制)
缓存友好性 一般 高(着色 + 对象复用)
分配速度 中等(可能触发回收) 极快(常从 per-CPU 缓存获取)
关键 API:
// 创建 slab 缓存
struct kmem_cache *kmem_cache_create(const char *name,
                                     unsigned int size,
                                     unsigned int align,
                                     slab_flags_t flags,
                                     void (*ctor)(void *));

// 从缓存分配对象
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);

// 释放对象回缓存
void kmem_cache_free(struct kmem_cache *cachep, void *objp);

// 销毁缓存
void kmem_cache_destroy(struct kmem_cache *cachep);

1.4 vmalloc:虚拟地址连续性的救星

vmalloc 用于分配虚拟地址连续但物理地址可能不连续的大块内存。它通过修改内核页表,将多个不连续的物理页映射到一段连续的虚拟地址空间。适用于需要大缓冲区但不需要物理连续性的场景(如模块加载、某些驱动程序)。

性能代价:vmalloc 每次分配都需要操作页表(TLB 刷新),速度远慢于 slab 或 kmalloc。此外,vmalloc 区域大小受 vmalloc_total 限制(通常为 128MB ~ 256MB)。
典型用途:内核模块加载、帧缓冲驱动、大块临时缓冲区。
内核内存分配器层次架构 用户态 malloc / 内核态 kmalloc / vmalloc 调用 分配器路由:kmalloc → slab/slub | vmalloc → 页表映射 slab / slub 分配器 对象缓存、per-CPU 池、着色 vmalloc 区域 页表映射、非连续物理页 伙伴系统(Buddy System)— 物理页帧管理 物理内存页帧(ZONE_DMA / ZONE_NORMAL / ZONE_HIGHMEM)

1.5 三者在内存管理中的定位总结

伙伴系统、slab 和 vmalloc 构成了内核内存分配的三层体系。伙伴系统是地基,管理物理页;slab 是上层建筑,优化小对象分配;vmalloc 是特殊通道,解决虚拟地址连续性问题。理解它们的定位与协作关系,是进行内存分配优化的第一步。

分配器 物理连续性 虚拟连续性 分配粒度 速度 典型场景
伙伴系统 是 是 4KB ~ 4MB 中等 DMA 缓冲区、页表、大块内核数据
slab 是 是 8B ~ 8KB 极快 task_struct、inode、dentry、网络缓冲区
vmalloc 否 是 页(4KB)~ 数十 MB 慢 模块加载、帧缓冲、大块临时缓冲区
工程建议:在编写内核模块或驱动时,优先使用 kmalloc(底层走 slab)分配小于一页的对象;对于大于一页但不需要物理连续性的缓冲区,考虑 vmalloc;只有在明确需要物理连续大块内存(如 DMA)时才直接使用 alloc_pages。错误的分配器选择会导致性能下降或内存浪费。