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)。
常见陷阱:伙伴系统虽然能有效减少外部碎片,但内部碎片(分配 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)。典型用途:内核模块加载、帧缓冲驱动、大块临时缓冲区。
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。错误的分配器选择会导致性能下降或内存浪费。