目录
在 Linux 内核中,伙伴分配器(Buddy Allocator)和 SLAB 分配器(SLAB Allocator)是内存管理系统的两大核心组件,它们协同工作实现了高效的内存分配。下面我将详细解释它们的工作原理、区别和协同关系。
一、伙伴分配器(Buddy Allocator) - 大内存分配专家
1.1 伙伴分配器核心概念表格
术语 | 定义 |
---|---|
页(Page) | 内存管理的基本单位,通常为4KB。 |
页块 | 由连续页组成的内存块,大小为 2^n 页。 |
阶(Order) | 表示页块的大小,n 阶页块包含 2^n 个连续页。例如:0阶=1页,1阶=2页。 |
伙伴(Buddy) | 满足以下条件的同阶页块: 1. 物理地址连续。 2. 起始地址是 2^n 的倍数。3. 合并后起始地址是 2^{n+1} 的倍数。 |
内部碎片 | 分配的页块大小必须是 2^n ,可能导致实际使用内存小于分配内存。 |
外部碎片 | 无法合并的小空闲块,导致无法满足大块分配请求。 |
1.2 伙伴分配器分配流程
示例:分配100KB
- 计算所需阶数:100KB ≈ 25页 → 需要5阶(
2^5=32页
)。 - 查找5阶空闲页块:
- 若无空闲 → 查找6阶页块,分裂为两个5阶页块。
- 分配一个5阶页块(32页),剩余5阶页块加入空闲链表。
1.3 伙伴分配器释放流程
释放页块 → 检查其伙伴是否空闲:
✅ 伙伴空闲 → 合并为更高阶页块 → 递归检查合并;
❌ 无法合并 → 将页块加入对应阶的空闲链表。
示例:释放64KB
- 释放一个6阶页块(64页)。
- 检查伙伴是否空闲:
- 若伙伴空闲 → 合并为7阶页块(128页)。
- 继续检查更高阶伙伴是否可合并,直到无法合并为止。
1.4 伙伴分配器数据结构表格
数据结构 | 作用 |
---|---|
free_area 数组 | 每个内存区域维护一个数组,下标对应阶数(0~MAX_ORDER)。 |
free_list 链表 | 每个阶数维护多个链表(按迁移类型划分),存储空闲页块。 |
nr_free | 记录当前阶的空闲页块总数。 |
per_cpu_pages | 每个CPU缓存小阶页块(如0~3阶),减少锁竞争,提升单页分配性能。 |
1.5 伙伴分配器优缺点对比表
优点 | 缺点 |
---|---|
1. 分配效率高:按阶管理,避免线性扫描。 | 1. 内部碎片:分配的页块大小必须是 2^n ,可能导致内存浪费。 |
2. 减少外部碎片:释放时合并伙伴页块。 | 2. 释放时需记录分配阶数,调用者需提供正确参数。 |
3. 支持NUMA架构:优先分配本地节点内存。 | 3. 固定大小的分割可能不适用于所有场景(需配合Slab分配器)。 |
1.6 伙伴分配器与Slab分配器协作关系
伙伴分配器 | Slab分配器 |
---|---|
管理大块内存(如4KB~4MB)。 | 管理小对象(如内核数据结构)。 |
分配单位:阶(2^n 页)。 | 分配单位:固定大小的内存块(Slab)。 |
直接分配物理页块。 | 从伙伴分配器获取大块内存,划分为小对象。 |
优化目标:减少外部碎片。 | 优化目标:减少内部碎片。 |
1.7 查看伙伴系统状态
cat /proc/buddyinfo
# 输出示例:
# Node 0, zone DMA 1 1 0 0 2 ...
# 表示DMA区域有:
# 1个order0块(4KB), 1个order1块(8KB), 0个order2块(16KB)...
图片显示的是 Linux 系统中 /proc/pagetypeinfo
文件的内容,该文件提供了关于系统内存页面类型的详细信息。以下是对其内容的详细分析:
Page block order 和 Pages per block
- Page block order: 9
- 这表示每个内存块的阶数(order)为 9。
- Pages per block: 512
- 每个内存块包含 512 个页面(
2^9 = 512
)。
- 每个内存块包含 512 个页面(
Free pages count per migrate type at order
这一部分展示了在不同迁移类型(migrate type)和阶数(order)下的空闲页面数量。
-
Node 0, zone DMA32, type Unmovable
- 在
DMA32
区域中,Unmovable
类型的空闲页面在各个阶数上的数量均为 0。
- 在
-
Node 0, zone DMA32, type Movable
- 在
DMA32
区域中,Movable
类型的空闲页面在阶数 0 到 10 上的数量分别为:4, 5, 4, 3, 6, 5, 4, 4, 4, 4, 960。
- 在
-
Node 0, zone DMA32, type Reclaimable
- 在
DMA32
区域中,Reclaimable
类型的空闲页面在各个阶数上的数量均为 0。
- 在
-
Node 0, zone DMA32, type HighAtomic
- 在
DMA32
区域中,HighAtomic
类型的空闲页面在各个阶数上的数量均为 0。
- 在
-
Node 0, zone DMA32, type Isolate
- 在
DMA32
区域中,Isolate
类型的空闲页面在各个阶数上的数量均为 0。
- 在
-
Node 0,
Normal
区域中, zone Normal, type Unmovable
-Unmovable
类型的 在空闲页面在阶数 0 到 10 上的数量分别为:0, 175, 69, 50, 50, 15, 4, 3, 0, 0, 0。 -
Node 0, zone Normal, type Movable
- 在
Normal
区域中,Movable
类型的空闲页面在阶数 0 到 10 上的数量分别为:352, 189, 54, 27, 24, 14, 6, 6, 6, 3, 2690。
- 在
-
Node 0, zone Normal, type Reclaimable
- 在
Normal
区域中,Reclaimable
类型的空闲页面在阶数 0 到 10 上的数量分别为:24, 50, 95, 56, 25, 13, 0, 0, 0, 0, 0。
- 在
-
Node 0, zone Normal, type HighAtomic
- 在
Normal
区域中,HighAtomic
类型的空闲页面在各个阶数上的数量均为 0。
- 在
-
Node 0, zone Normal, type Isolate
- 在
Normal
区域中,Isolate
类型的空闲页面在各个阶数上的数量均为 0。
- 在
Number of blocks type
这一部分展示了在不同迁移类型下的内存块数量。
-
Node 0, zone DMA32
Unmovable
: 3 个块Movable
: 1981 个块Reclaimable
: 0 个块HighAtomic
: 0 个块Isolate
: 0 个块
-
Node 0, zone Normal
Unmovable
: 373 个块Movable
: 5770 个块Reclaimable
: 17 个块HighAtomic
: 0 个块Isolate
: 0 个块
总结
- DMA32 区域 主要用于可以直接被硬件(如旧式显卡)访问的内存区域,通常在 32 位系统或特定硬件需求下使用。
- Normal 区域 是标准的内存区域,适用于大多数内存分配需求。
- Unmovable 类型 的页面通常是内核代码、某些驱动程序数据等不能被移动的页面。
- Movable 类型 的页面是可以被移动的,常见于用户空间的内存分配。
- Reclaimable 类型 的页面是可以被回收的,如缓存页面。
- HighAtomic 类型 的页面用于原子操作,确保在高并发情况下的内存分配。
- Isolate 类型 的页面通常用于内存隔离,防止被其他进程访问。
- 内存分配倾向:系统在 DMA32 和 Normal 区域都倾向于为可移动的内存分配保留较多资源,这有助于内核进行内存碎片整理和动态内存管理。
- 内存碎片化情况:从高阶数空闲页数量来看,Normal 区域的可移动内存有较多大页块可用,说明该区域的内存碎片化程度相对较低;而不可移动和可回收内存的高阶数空闲页较少,可能存在一定的碎片化问题。
- 迁移类型使用情况:可移动内存类型在两个区域的使用最为广泛,而高原子和隔离类型的内存几乎未被使用,可能是因为系统当前的工作负载不需要这些特殊的内存分配类型。
1.8 伙伴分配器分配内存示例
我们假设系统有 16 个连续的物理页(64KB,每页 4KB),初始状态如下:
分配请求 1:申请 3 页内存
-
计算最小满足阶数:
- 3 页不是 2^n,向上取整到 4 页(2^2)
- 需要 Order 2 的块(4 页)
-
查找空闲块:
- Order 2 链表:空
- Order 3 链表:空
- Order 4 链表:有 1 个 16 页块
-
分裂高阶块:
-
继续分裂:
- Order 2 仍为空,继续分裂 Order 3:
-
分配并更新:
- 分配 0x0000 的 Order 2 块(4 页)
- 剩余空闲块:
Order 2: 0x1000 (4页) Order 3: 0x2000 (8页)
分配请求 2:申请 1 页内存
-
计算最小满足阶数:
- 1 页 = 2^0 → 需要 Order 0 块
-
查找空闲块:
- Order 0 链表:空
- 最低可用块:Order 2 的 0x1000(4 页)
-
分裂过程:
-
分配并更新:
- 分配 0x1000 的 Order 0 块(1 页)
- 剩余空闲块:
Order 0: 0x1400 (1页) Order 1: 0x1800 (2页) Order 3: 0x2000 (8页)
分配请求 3:申请 6 页内存
-
计算最小满足阶数:
- 6 页 → 向上取整到 8 页(2^3)
- 需要 Order 3 块
-
查找空闲块:
- Order 3 链表:有 0x2000(8 页)
-
直接分配:
- 分配整个 Order 3 块
- 剩余空闲块:
Order 0: 0x1400 (1页) Order 1: 0x1800 (2页)
内存释放场景
释放第一次分配的 4 页(0x0000)
- 释放 Order 2 块:
- 检查伙伴块:地址计算规则
伙伴地址 = 块地址 ^ (1 << (order + PAGE_SHIFT)) 0x0000 ^ 0x4000 = 0x4000
- 0x4000 无空闲块 → 直接加入 Order 2 链表
- 空闲块更新:
Order 0: 0x1400 Order 1: 0x1800 Order 2: 0x0000
释放第二次分配的 1 页(0x1000)
- 释放 Order 0 块:
- 检查伙伴 0x1400(相同 Order 0)
- 0x1400 是空闲块 → 合并为 Order 1 块(0x1000-0x17FF)
- 再检查新块的伙伴:0x1800(Order 1)
- 0x1800 是空闲块 → 合并为 Order 2 块(0x1000-0x1FFF)
- 空闲块更新:
Order 2: 0x0000 Order 2: 0x1000(新合并)
位图更新示例
初始位图(Order 0-4):
Order 4: [1] // 1表示空闲
Order 3: [0,0]
Order 2: [0,0,0,0]
Order 1: [0,0,0,0,0,0,0,0]
Order 0: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
分配 4 页后:
Order 4: [0]
Order 3: [1,0] // 0x2000空闲
Order 2: [0,1,0,0] // 0x1000空闲
Order 1: [0,0,0,0,0,0,0,0]
Order 0: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
1.9 伙伴分配器关键特性总结
1. 地址计算规则
伙伴块地址计算公式:伙伴地址 = 块地址 ^ (1 << (order + PAGE_SHIFT))
- 示例:Order 1 块(2 页)在 0x1000:
0x1000 ^ (1 << (1+12)) = 0x1000 ^ 0x2000 = 0x3000
2. 内部碎片问题
请求大小 | 实际分配 | 碎片率 |
---|---|---|
1 页 | 1 页 | 0% |
3 页 | 4 页 | 25% |
5 页 | 8 页 | 37.5% |
7 页 | 8 页 | 12.5% |
二、SLAB分配器 - 小对象分配大师
2.1 核心功能
- 管理内核对象(task_struct, inode等)
- 分配小内存(几十字节到几KB)
- 减少内部碎片和初始化开销
2.2 三级架构
2.3 核心概念
组件 | 说明 |
---|---|
对象(Object) | SLAB中分配的实际内存单元 |
缓存(Cache) | 同类型对象的集合(如task_struct缓存) |
SLAB | 由1个或多个连续页组成的内存块 |
2.4 工作流程
- 创建缓存:
kmem_cache_create("task_struct", sizeof(task_struct), ...)
- 分配对象:
task = kmem_cache_alloc(task_struct_cache, GFP_KERNEL)
- 释放对象:
kmem_cache_free(task_struct_cache, task)
- 销毁缓存:
kmem_cache_destroy(task_struct_cache)
2.5 SLAB状态
cat /proc/slabinfo
# 输出示例:
# name active_objs num_objs objsize objperslab
# task_struct 120 150 1792 4
表示:
-
每个task_struct对象1792字节
-
每个SLAB容纳4个对象
-
总共150个对象(37.5个SLAB)
-
活跃对象120个
/proc/slabinfo
文件提供了内核 slab 分配器的详细信息。下面对输出的每列信息进行解释: -
slabinfo - version: 2.1
:表示当前slabinfo
的版本是 2.1。 -
表头各列的含义:
name
:slab 缓存的名称,通常与使用该缓存的内核数据结构或设备相关。<active_objs>
:当前活跃对象的数量,即正在被使用的对象数量。<num_objs>
:缓存中对象的总数。<objsize>
:每个对象的大小(以字节为单位)。<objperslab>
:每个 slab 中对象的数量。<pagesperslab>
:每个 slab 所占用的物理页面数量。tunables
:包括<limit>
、<batchcount>
和<sharedfactor>
,这些是 slab 缓存的可调参数,用于控制缓存的行为。slabdata
:包含<active_slabs>
、<num_slabs>
和<sharedavail>
,分别表示活跃 slab 的数量、总 slab 的数量和共享可用的数量。
以 ext4_groupinfo_4k
这行为例:
ext4_groupinfo_4k 55 56 288 28 2 : tunables 0 0 0 : slabdata 2 2 0
ext4_groupinfo_4k
:这是一个与 ext4 文件系统组信息相关的 slab 缓存名称。<active_objs> = 55
:表示当前有 55 个对象正在被使用。<num_objs> = 56
:表示该缓存中总共有 56 个对象。<objsize> = 288
:每个对象的大小是 288 字节。<objperslab> = 28
:每个 slab 中包含 28 个对象。<pagesperslab> = 2
:每个 slab 占用 2 个物理页面。tunables
部分的<limit> = 0
、<batchcount> = 0
、<sharedfactor> = 0
:这些可调参数目前都被设置为 0。slabdata
部分:<active_slabs> = 2
:表示有 2 个活跃的 slab。<num_slabs> = 2
:表示总共有 2 个 slab。<sharedavail> = 0
:共享可用的数量为 0。
很多 slab 缓存的活跃对象和对象总数为 0,这可能意味着对应的内核数据结构或设备当前没有在使用或者处于空闲状态。而像 ext4_groupinfo_4k
、ext4_groupinfo_1k
、can_receiver
等有一定数量的活跃对象,说明这些相关的内核功能正在运行并使用相应的 slab 缓存来管理内存。
三、伙伴分配器 vs SLAB分配器 : 协同工作流程
特性 | 伙伴分配器 | SLAB分配器 |
---|---|---|
管理单元 | 物理页框 | 内核对象 |
分配粒度 | 4KB~几MB | 几十字节~几KB |
主要目标 | 大块连续内存 | 小对象高效分配 |
碎片处理 | 解决外部碎片 | 解决内部碎片 |
性能特点 | O(log n)分配 | O(1)分配 |
使用场景 | 页表分配、DMA缓存 | 进程描述符、文件对象 |
欢迎阅读下一篇:Linux 内存管理(2):了解内存回收机制