一、Buddy System
Buddy System(伙伴系统)是操作系统内核中管理物理内存页框(Page Frame) 的核心算法,专为解决外部碎片和大块连续物理内存分配问题而设计。
1. 关键数据结构
// 1. 内存区域描述符 (include/linux/mmzone.h)
struct zone {
struct free_area free_area[MAX_ORDER]; // 空闲区域数组(每个阶一个) MAX_ORDER:最大阶数(通常为 11),支持分配 $2^{10}$ = 1024 页(4MiB)。
...
};
// 2. 空闲块管理器 (include/linux/mmzone.h)
struct free_area {
struct list_head free_list[MIGRATE_TYPES]; // 按迁移类型分组的链表, MIGRATE_TYPES:迁移类型(如 MIGRATE_UNMOVABLE),用于抗碎片策略。
unsigned long nr_free; // 空闲块数量
};
// 3. 页框描述符 (include/linux/mm_types.h)
struct page {
unsigned long flags; // 状态标志(如是否空闲)
struct list_head lru; // 链接到 free_list 的指针
...
};
2. 内存分配(alloc_pages)
2.1 get_page_from_freelist
alloc_pages() → __alloc_pages() →
static struct page * get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags, const struct alloc_context *ac)
关键代码段解析
a. 区域遍历机制
for_next_zone_zonelist_nodemask(zone, z, ac->highest_zoneidx, ac->nodemask) //按节点/区域优先级遍历 zonelist
b. 水位检测(Watermark Check)
水位类型:
ALLOC_WMARK_MIN:最低水位(紧急情况); ALLOC_WMARK_LOW:低水位(默认); ALLOC_WMARK_HIGH:高水位(积极回收)
快速路径:zone_watermark_fast 使用每CPU缓存快速判断
mark = wmark_pages(zone, alloc_flags & ALLOC_WMARK_MASK);
if (!zone_watermark_fast(zone, order, mark, ac->highest_zoneidx, alloc_flags))
c. 节点回收(Node Reclaim)
触发条件:水位检查失败 && node_reclaim_mode 启用
回收内容:页面缓存(Page Cache); Slab; 缓存非活动匿名页
ret = node_reclaim(zone->zone_pgdat, gfp_mask, order);
switch (ret) {
case NODE_RECLAIM_NOSCAN: ...
case NODE_RECLAIM_FULL: ...
default: ...
}
d. 物理页分配核心
page = rmqueue(ac->preferred_zoneref->zone, zone, order,
gfp_mask, alloc_flags, ac->migratetype);
e. 高阶原子分配保留
场景:原子分配(GFP_ATOMIC)且 order > 0
机制:保留整个页块用于未来原子分配
if (unlikely(order && (alloc_flags & ALLOC_HARDER)))
reserve_highatomic_pageblock(page, zone, order);
f. 延迟初始化处理
背景:启动时延迟初始化页结构加速启动
处理:首次访问时触发初始化
#ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
if (static_branch_unlikely(&deferred_pages)) {
if (_deferred_grow_zone(zone, order))
goto try_this_zone;
}
#endif
2.2 rmqueue
static struct page * get_page_from_freelist() →
static inline struct page *rmqueue(struct zone *preferred_zone,
struct zone *zone, unsigned int order,
gfp_t gfp_flags, unsigned int alloc_flags,
int migratetype)
关键代码段解析
1. 单页分配快速路径(order=0)
使用每CPU页缓存(pcplist)避免全局锁竞争
时间复杂度 O(1),无锁操作
占所有内存分配的 70%+(实证数据)
if (likely(order == 0)) {
page = rmqueue_pcplist(preferred_zone, zone, gfp_flags,
migratetype, alloc_flags);
goto out;
}
2. 高阶分配路径(order>0)
spin_lock_irqsave(&zone->lock, flags); // 获取区域锁
do {
// 分配逻辑
} while (...);
spin_unlock(&zone->lock); // 释放锁
3. 分配优先级策略:
a. 高优先级原子分配(ALLOC_HARDER):
使用 MIGRATE_HIGHATOMIC 保留区
为原子分配(如中断处理)保留资源
if (order > 0 && alloc_flags & ALLOC_HARDER) {
page = __rmqueue_smallest(zone, order, MIGRATE_HIGHATOMIC);
}
b. CMA区域分配:
条件:可移动类型 + CMA标志
用于连续内存分配(如DMA缓冲区)
if (migratetype == MIGRATE_MOVABLE && alloc_flags & ALLOC_CMA) {
page = __rmqueue_cma(zone, order, migratetype, alloc_flags);
}
c. 常规伙伴分配:
核心函数 __rmqueue
可能触发备用迁移类型回退
if (!page) {
page = __rmqueue(zone, order, migratetype, alloc_flags);
}
3. 内存释放(free_pages)
free_pages() → __free_pages_ok() → free_one_page()
合并逻辑(核心函数 __free_one_page()):
while (order < MAX_ORDER - 1) {
buddy_pfn = __find_buddy_pfn(pfn, order); // 计算伙伴页框号
if (!page_is_buddy(page, buddy_pfn, order))
break;
list_del(&buddy_page->lru); // 从链表移除伙伴
pfn = min(pfn, buddy_pfn); // 合并后的起始页框号
order++; // 阶数提升
}
add_to_free_area(pfn_to_page(pfn), &zone->free_area[order]); // 插入新块
二、Slab Alloctaor
Linux 内核里把“slab”当成一个总称,实际上有三种实现:SLAB、SLUB、SLOB。它们都位于“伙伴系统”之上,专门给内核对象做高速、低碎片的缓存分配。
三种分配器对比速查表
Slab Allocator 是 Linux 内核中用于高效管理内核对象的内存分配器,解决伙伴系统的两大痛点:
- 小对象分配效率:伙伴系统最小分配单位是页(4KB),分配小对象浪费严重;
- 频繁分配/释放开销:内核对象(如 task_struct)频繁创建销毁,初始化开销大;
1. 关键数据结构
1.1 kmem_cache - 缓存控制器
// include/linux/slub_def.h
struct kmem_cache {
struct kmem_cache_cpu __percpu *cpu_slab; // 每CPU控制结构
slab_flags_t flags; // 标志位(调试、对齐等)
unsigned int min_partial; // 最小保留partial slab数量
unsigned int size; // 对象实际大小(含元数据)
unsigned int object_size; // 原始对象大小
struct kmem_cache_order_objects oo; // 包含对象数和阶数
// NUMA节点管理
struct kmem_cache_node *node[MAX_NUMNODES];
// 关键函数指针
void (*ctor)(void *); // 对象构造函数
const char *name; // 缓存名称
struct list_head list; // 全局缓存链表
// ... 其他统计字段
};
1.2 kmem_cache_cpu - 每CPU快速通道
struct kmem_cache_cpu {
void **freelist; // 指向当前空闲对象链表
struct page *page; // 当前正在使用的slab页
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct page *partial; // CPU本地partial列表
unsigned int nr_partial; // partial列表计数
#endif
int node; // 当前slab所属NUMA节点
};
1.3 kmem_cache_node - 节点管理
struct kmem_cache_node {
spinlock_t lock; // 保护节点操作的锁
unsigned long nr_partial; // partial slab数量
struct list_head partial; // partial slab链表
#ifdef CONFIG_SLUB_DEBUG
atomic_long_t nr_slabs; // 总slab计数(调试)
atomic_long_t total_objects; // 对象总数(调试)
struct list_head full; // full slab链表(调试)
#endif
};
1.4 页元数据整合
// include/linux/mm_types.h
struct page {
union {
// SLUB专用字段
struct {
void *freelist; // 页内空闲对象链表
struct kmem_cache *slab_cache; // 所属缓存
union {
struct {
unsigned inuse:16; // 使用中对象数
unsigned objects:15; // 总对象数
unsigned frozen:1; // 冻结状态
};
void *pad;
};
};
};
};
2. 内存分配(slab_alloc)
// mm/slub.c
static __always_inline void *slab_alloc(
struct kmem_cache *s, gfp_t gfpflags, unsigned long addr)
{
struct kmem_cache_cpu *c;
void *object;
// 获取当前CPU的slab控制结构
c = raw_cpu_ptr(s->cpu_slab);
// 快速路径:直接从freelist分配
object = c->freelist;
if (unlikely(!object || !node_match(c, node)))
goto redo; // 慢速路径
c->freelist = get_freepointer(s, object);
return object;
redo:
// 慢速路径处理
return __slab_alloc(s, gfpflags, addr, c);
}
慢速路径分配(__slab_alloc)
static void *__slab_alloc(struct kmem_cache *s, ...)
{
struct page *new;
// 1. 检查本地partial列表
if (kmem_cache_debug(s) && !alloc_debug_processing(s, page, addr))
goto new_slab;
// 2. 尝试从当前page分配
if (unlikely(!node_match(c, node)))
deactivate_slab(s, page, c->freelist); // 释放当前slab
// 3. 从CPU partial列表获取
if (c->partial) {
page = c->partial;
c->partial = page->next;
c->freelist = page->freelist;
goto load_freelist;
}
// 4. 从节点partial列表获取
spin_lock(&n->lock);
if (n->partial) {
page = list_first_entry(&n->partial);
list_del(&page->lru);
c->page = page;
c->freelist = page->freelist;
spin_unlock(&n->lock);
goto load_freelist;
}
new_slab:
// 5. 分配新slab
new = new_slab(s, gfpflags, node);
c->page = new;
c->freelist = new->freelist;
return c->freelist;
}
新slab创建(new_slab)
static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
{
struct page *page;
void *start, *end;
// 1. 从伙伴系统分配内存页
page = alloc_slab_page(s, flags, node);
// 2. 初始化slab元数据
start = page_address(page);
end = start + (PAGE_SIZE << oo_order(s->oo));
// 3. 构建空闲链表
last = start;
for (i = 0; i < page->objects; i++) {
void *object = last;
set_freepointer(s, object, last + s->size); // 嵌入式空闲指针
last += s->size;
}
set_freepointer(s, last - s->size, NULL); // 链表结束
// 4. 初始化页状态
page->freelist = start;
page->inuse = 0;
page->frozen = 1;
return page;
}
3. 对象释放(slab_free)
static __always_inline void slab_free(
struct kmem_cache *s, struct page *page, void *x, unsigned long addr)
{
struct kmem_cache_cpu *c = this_cpu_ptr(s->cpu_slab);
// 快速路径:释放到当前CPU的slab
if (likely(page == c->page && c->node >= 0)) {
set_freepointer(s, x, c->freelist); // 将对象插入空闲链表
c->freelist = x;
return;
}
// 慢速路径
__slab_free(s, page, x, addr);
}
三、Buddy & Slab 总结
1. 差异性
2. 基本框架
3. 典型问题分析
Q:为什么需要两级分配器?
- Buddy 单独工作:分配小对象时产生严重内部碎片(如分配 100B 需占用 4KB 页)。
- Slab 单独工作:无法获取物理连续内存(Slab 依赖 Buddy 提供连续页)。
- 协同优势:Buddy 提供大块连续页 → Slab 细分为小对象 → 兼顾外部碎片控制与小对象高效分配。 Q:kmalloc() 如何关联两者?
- kmalloc(size) 使用通用 Slab 缓存(如 kmalloc-4k, kmalloc-8k)。
- 若 size > 8KB(可配置),则回退到 Buddy 分配(相当于调用 alloc_pages)。 Q:何时直接使用 Buddy?
- 需求明确为大块连续物理内存时(如设备 DMA 要求物理连续)。
- 驱动中常见: // 申请 64KB 连续物理内存 page = alloc_pages(GFP_DMA | __GFP_ZERO, 4); // Order=4 (16页=64KB if 4KB/page) addr = page_address(page);
四、调试技巧
1. 查看各阶空闲块数量
cat /proc/buddyinfo
Node 0, zone DMA 0 0 0 0 0 0 0 1 0 1 3
Node 0, zone DMA32 12315 5098 2232 1801 946 588 376 211 83 55 8
Node 0, zone Normal 1665 1293 718 424 131 79 38 22 10 5 2
第 k 列表示阶数为 k 的空闲块数量(单位:$2^k$ 页)
2. 显示各迁移类型的详细分布
cat /proc/pagetypeinfo
Page block order: 9
Pages per block: 512
Free pages count per migrate type at order 0 1 2 3 4 5 6 7 8 9 10
Node 0, zone DMA, type Unmovable 0 0 0 0 0 0 0 1 0 0 0
Node 0, zone DMA, type Movable 0 0 0 0 0 0 0 0 0 1 3
Node 0, zone DMA, type Reclaimable 0 0 0 0 0 0 0 0 0 0 0
Node 0, zone DMA, type HighAtomic 0 0 0 0 0 0 0 0 0 0 0
Node 0, zone DMA, type Isolate 0 0 0 0 0 0 0 0 0 0 0
Node 0, zone DMA32, type Unmovable 1758 929 343 121 33 5 3 3 1 2 1
Node 0, zone DMA32, type Movable 9166 4074 1587 1149 778 567 365 204 82 44 7
Node 0, zone DMA32, type Reclaimable 1237 13 258 494 117 0 0 0 0 0 0
Node 0, zone DMA32, type HighAtomic 154 82 44 37 18 16 8 4 0 9 0
Node 0, zone DMA32, type Isolate 0 0 0 0 0 0 0 0 0 0 0
Node 0, zone Normal, type Unmovable 1033 625 375 198 27 6 0 0 0 0 0
Node 0, zone Normal, type Movable 157 346 289 200 100 71 38 22 10 5 2
Node 0, zone Normal, type Reclaimable 482 253 114 43 0 0 0 0 0 0 0
Node 0, zone Normal, type HighAtomic 73 41 36 28 6 2 0 0 0 0 0
Node 0, zone Normal, type Isolate 0 0 0 0 0 0 0 0 0 0 0
Number of blocks type Unmovable Movable Reclaimable HighAtomic Isolate
Node 0, zone DMA 1 7 0 0 0
Node 0, zone DMA32 96 1259 158 15 0
Node 0, zone Normal 111 359 37 5 0