难度 : 🟡 进阶级
预计学习时间 : 70分钟
前置知识 : 03-DRM子系统与AMDGPU架构
📋 概述
数据结构是算法的骨架。理解Buddy的数据结构是掌握其算法的关键:
- 🏗️ struct drm_buddy: 分配器的大脑,管理整个VRAM
- 🧱 struct drm_buddy_block: 内存块节点,构成二叉树
- 🎯 header字段: 巧妙的位域设计,压缩存储多种信息
- 🔗 链表与树: 双重数据结构,兼顾查找和合并效率
本章深入剖析这些数据结构的设计思想和实现细节。
4.1 struct drm_buddy - 分配器主结构
完整定义
c
// include/drm/drm_buddy.h
struct drm_buddy {
/* 每个order维护一个空闲链表 */
struct list_head *free_list;
/* 二叉树根节点数组(可能有多棵树)*/
struct drm_buddy_block **roots;
/* ===== 以下是公开的不可变字段 ===== */
unsigned int n_roots; // 根节点数量
unsigned int max_order; // 最大阶数
u64 chunk_size; // 最小分配单元(通常4KB)
u64 size; // 总大小(字节)
u64 avail; // 可用空间(字节)
u64 clear_avail; // 已清零的可用空间
};
字段详解
1. free_list - 空闲链表数组
c
struct list_head *free_list;
// 初始化时动态分配
mm->free_list = kmalloc_array(mm->max_order + 1,
sizeof(*mm->free_list),
GFP_KERNEL);
// 每个order一个链表
// free_list[0] → Order 0 的空闲块链表
// free_list[1] → Order 1 的空闲块链表
// ...
// free_list[max_order] → 最大order的空闲块链表
可视化:
free_list 数组 (max_order = 10):
[0] → [4KB块1] → [4KB块2] → [4KB块3] → NULL
[1] → [8KB块1] → [8KB块2] → NULL
[2] → [16KB块] → NULL
[3] → NULL (没有32KB的空闲块)
[4] → [64KB块] → NULL
...
[10] → [4MB块] → NULL
特点:
- O(1) 查找指定order的空闲块
- 块按order升序排列(便于合并相邻块)
- 分配时从链表头取出
2. roots - 二叉树根数组
c
struct drm_buddy_block **roots;
unsigned int n_roots;
// 为什么需要多个根?
// 因为VRAM大小可能不是2的幂次
示例:
场景: VRAM大小 = 12MB = 8MB + 4MB
不能用单棵二叉树表示12MB,因为12不是2的幂次
解决: 分解成2的幂次之和
12MB = 8MB + 4MB
= 2^3 MB + 2^2 MB
roots[0] → [8MB 树]
/ \
[4MB] [4MB]
/ \ / \
... ... ...
roots[1] → [4MB 树]
/ \
[2MB] [2MB]
/ \ / \
... ... ...
n_roots = 2
计算n_roots:
c
// 计算需要多少棵树
static int split_block_prepare(struct drm_buddy *mm, u64 start, u64 size)
{
// 对size进行二进制表示
// 例如: 12 = 0b1100 = 8 + 4
// 有2个'1'位,需要2棵树
n_roots = hweight64(size / chunk_size);
return n_roots;
}
// 示例
size = 12MB, chunk_size = 4KB
chunks = 12MB / 4KB = 3072 = 0b110000000000
hweight64(3072) = 2 个'1'位
n_roots = 2
3. max_order - 最大阶数
c
unsigned int max_order;
// 计算公式
max_order = ilog2(size / chunk_size);
// 示例
size = 8GB, chunk_size = 4KB
max_order = ilog2(8GB / 4KB)
= ilog2(2M)
= ilog2(2^21)
= 21
约束:
c
#define DRM_BUDDY_MAX_ORDER (63 - 12) // = 51
// 为什么是51?
// - header字段用6位存储order (bit 0-5)
// - 最大可表示 2^6 - 1 = 63
// - 但实际限制为51,因为:
// - chunk_size至少4KB (2^12)
// - 总大小最大 4KB * 2^51 = 8PB
// - 足够大的范围
4. chunk_size - 最小单元
c
u64 chunk_size;
// 必须满足
// 1. 至少4KB (页面大小)
// 2. 必须是2的幂次
// 初始化检查
int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
{
if (chunk_size < SZ_4K)
return -EINVAL;
if (!is_power_of_2(chunk_size))
return -EINVAL;
mm->chunk_size = chunk_size;
// ...
}
5. avail - 可用空间统计
c
u64 avail; // 可用空间(字节)
u64 clear_avail; // 已清零的可用空间
// 初始化时
mm->avail = size;
mm->clear_avail = 0; // 初始全部未清零
// 分配时减少
static void mark_allocated(struct drm_buddy_block *block)
{
block->header &= ~DRM_BUDDY_HEADER_STATE;
block->header |= DRM_BUDDY_ALLOCATED;
list_del(&block->link);
// 调用者更新统计
mm->avail -= drm_buddy_block_size(mm, block);
}
// 释放时增加
static void mark_free(struct drm_buddy *mm,
struct drm_buddy_block *block)
{
block->header &= ~DRM_BUDDY_HEADER_STATE;
block->header |= DRM_BUDDY_FREE;
list_insert_sorted(mm, block);
mm->avail += drm_buddy_block_size(mm, block);
}
// 查询可用空间
u64 drm_buddy_avail_size(struct drm_buddy *mm)
{
return mm->avail;
}
4.2 struct drm_buddy_block - 内存块表示
完整定义
c
// include/drm/drm_buddy.h
struct drm_buddy_block {
/* 核心字段:压缩多个信息到一个u64 */
u64 header;
/* 二叉树指针 */
struct drm_buddy_block *left; // 左子节点
struct drm_buddy_block *right; // 右子节点
struct drm_buddy_block *parent; // 父节点
/* 扩展字段 */
void *private; // 用户私有数据
/* 链表节点 */
struct list_head link; // 空闲链表或用户链表
struct list_head tmp_link; // 临时链表(内部使用)
};
header字段 - 巧妙的位域设计
header是一个64位整数,压缩了多种信息:
c
/* 位域定义 */
#define DRM_BUDDY_HEADER_OFFSET GENMASK_ULL(63, 12) // bit 12-63: 偏移
#define DRM_BUDDY_HEADER_STATE GENMASK_ULL(11, 10) // bit 10-11: 状态
#define DRM_BUDDY_ALLOCATED (1 << 10) // 01: 已分配
#define DRM_BUDDY_FREE (2 << 10) // 10: 空闲
#define DRM_BUDDY_SPLIT (3 << 10) // 11: 已分裂
#define DRM_BUDDY_HEADER_CLEAR GENMASK_ULL(9, 9) // bit 9: 清零标志
#define DRM_BUDDY_HEADER_UNUSED GENMASK_ULL(8, 6) // bit 6-8: 保留
#define DRM_BUDDY_HEADER_ORDER GENMASK_ULL(5, 0) // bit 0-5: order
/* 位域布局 (64位) */
┌─────────────────────────────────────────────────────┐
│ 63 12│ 11 10 │ 9 │8 6 │5 0 │
│ offset (52位) │ state │ C │保留 │ order (6位) │
│ │ (2位 )│ │ │ │
└─────────────────────────────────────────────────────┘
C = Clear flag (是否已清零)
为什么这样设计?
优势:
✓ 节省内存: 一个u64替代多个字段
✓ 原子操作: 可以原子性地读写
✓ Cache友好: 减少内存占用,提高cache命中率
权衡:
× offset只有52位: 限制最大地址空间 4PB (足够大)
× order只有6位: 限制最大order为63 (实际用51)
提取和设置字段
c
/* 提取offset */
static inline u64 drm_buddy_block_offset(struct drm_buddy_block *block)
{
return block->header & DRM_BUDDY_HEADER_OFFSET;
// 例如: header = 0x1234567890000003
// offset = 0x1234567890000000 (屏蔽低12位)
}
/* 提取order */
static inline unsigned int drm_buddy_block_order(struct drm_buddy_block *block)
{
return block->header & DRM_BUDDY_HEADER_ORDER;
// 例如: header = 0x1234567890000003
// order = 3 (只取低6位)
}
/* 提取状态 */
static inline unsigned int drm_buddy_block_state(struct drm_buddy_block *block)
{
return block->header & DRM_BUDDY_HEADER_STATE;
// 返回 DRM_BUDDY_ALLOCATED, FREE 或 SPLIT
}
/* 检查状态 */
static inline bool drm_buddy_block_is_allocated(struct drm_buddy_block *block)
{
return drm_buddy_block_state(block) == DRM_BUDDY_ALLOCATED;
}
static inline bool drm_buddy_block_is_free(struct drm_buddy_block *block)
{
return drm_buddy_block_state(block) == DRM_BUDDY_FREE;
}
static inline bool drm_buddy_block_is_split(struct drm_buddy_block *block)
{
return drm_buddy_block_state(block) == DRM_BUDDY_SPLIT;
}
/* 检查清零标志 */
static inline bool drm_buddy_block_is_clear(struct drm_buddy_block *block)
{
return block->header & DRM_BUDDY_HEADER_CLEAR;
}
/* 计算块大小 */
static inline u64 drm_buddy_block_size(struct drm_buddy *mm,
struct drm_buddy_block *block)
{
return mm->chunk_size << drm_buddy_block_order(block);
// size = chunk_size * 2^order
}
创建和销毁块
c
// drivers/gpu/drm/drm_buddy.c
// 块分配器(使用slab cache优化)
static struct kmem_cache *slab_blocks;
// 创建块
static struct drm_buddy_block *
drm_block_alloc(struct drm_buddy *mm,
struct drm_buddy_block *parent,
unsigned int order,
u64 offset)
{
struct drm_buddy_block *block;
block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
if (!block)
return NULL;
// 设置header: offset + order
block->header = offset;
block->header |= order;
// 设置父节点
block->parent = parent;
return block;
}
// 销毁块
static void drm_block_free(struct drm_buddy *mm,
struct drm_buddy_block *block)
{
kmem_cache_free(slab_blocks, block);
}
// 模块初始化时创建slab cache
static int __init drm_buddy_module_init(void)
{
slab_blocks = kmem_cache_create("drm_buddy_block",
sizeof(struct drm_buddy_block),
__alignof__(struct drm_buddy_block),
SLAB_PANIC,
NULL);
return 0;
}
4.3 块状态标志
三种状态
c
/* 状态定义 */
#define DRM_BUDDY_ALLOCATED (1 << 10) // 已分配给用户
#define DRM_BUDDY_FREE (2 << 10) // 空闲,在free_list中
#define DRM_BUDDY_SPLIT (3 << 10) // 已分裂,有子节点
状态转换图:
创建时
↓
[FREE] ←─────────────────┐
↓ 用户分配 │
[ALLOCATED] │
↓ 用户释放 │
[FREE] ──────────────────┘
↓ 需要更小的块
[SPLIT]
/ \
[FREE] [FREE]
状态操作函数
c
// drivers/gpu/drm/drm_buddy.c
/* 标记为已分配 */
static void mark_allocated(struct drm_buddy_block *block)
{
// 清除旧状态,设置ALLOCATED
block->header &= ~DRM_BUDDY_HEADER_STATE;
block->header |= DRM_BUDDY_ALLOCATED;
// 从空闲链表移除
list_del(&block->link);
}
/* 标记为空闲 */
static void mark_free(struct drm_buddy *mm,
struct drm_buddy_block *block)
{
// 清除旧状态,设置FREE
block->header &= ~DRM_BUDDY_HEADER_STATE;
block->header |= DRM_BUDDY_FREE;
// 插入到对应order的空闲链表(按offset排序)
list_insert_sorted(mm, block);
}
/* 标记为已分裂 */
static void mark_split(struct drm_buddy_block *block)
{
// 清除旧状态,设置SPLIT
block->header &= ~DRM_BUDDY_HEADER_STATE;
block->header |= DRM_BUDDY_SPLIT;
// 从空闲链表移除(因为已经分裂成子块)
list_del(&block->link);
}
清零标志
c
/* 清零标志操作 */
// 标记为已清零
static void mark_cleared(struct drm_buddy_block *block)
{
block->header |= DRM_BUDDY_HEADER_CLEAR;
}
// 清除清零标志
static void clear_reset(struct drm_buddy_block *block)
{
block->header &= ~DRM_BUDDY_HEADER_CLEAR;
}
// 检查是否已清零
static inline bool drm_buddy_block_is_clear(struct drm_buddy_block *block)
{
return block->header & DRM_BUDDY_HEADER_CLEAR;
}
使用场景:
c
// 分配时请求清零
int drm_buddy_alloc_blocks(struct drm_buddy *mm, ...,
unsigned long flags)
{
bool do_clear = flags & DRM_BUDDY_CLEAR_ALLOCATION;
// ... 分配块 ...
if (do_clear && !drm_buddy_block_is_clear(block)) {
// 需要清零但块未清零 → 执行清零
memset(vram_addr + offset, 0, size);
mark_cleared(block);
}
}
// 释放时保持清零状态(用于安全性)
void drm_buddy_free_block(struct drm_buddy *mm,
struct drm_buddy_block *block)
{
// 如果块之前已清零,释放后仍保持清零状态
// 这样下次分配就不需要重新清零
}
4.4 数据结构关系图
整体架构
┌─────────────────────────────────────────────────────┐
│ struct drm_buddy (分配器实例) │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ free_list[] (空闲链表数组) │ │
│ │ │ │
│ │ [0] → [4KB] → [4KB] → [4KB] → NULL │ │
│ │ [1] → [8KB] → [8KB] → NULL │ │
│ │ [2] → [16KB] → NULL │ │
│ │ [3] → NULL │ │
│ │ [4] → [64KB] → NULL │ │
│ │ ... │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ roots[] (二叉树根数组) │ │
│ │ │ │
│ │ [0] → ┌──────────┐ │ │
│ │ │ 8MB块 │ (SPLIT) │ │
│ │ │ order=11 │ │ │
│ │ └─────┬────┘ │ │
│ │ ┌─┴─────┐ │ │
│ │ ┌─────▼──┐ ┌──▼─────┐ │ │
│ │ │4MB块 │ │4MB块 │ (FREE) │ │
│ │ │order=10│ │order=10│ │ │
│ │ └──┬──┬──┘ └────────┘ │ │
│ │ │ │ │ │
│ │ ... ... │ │
│ │ │ │
│ │ [1] → ┌──────────┐ │ │
│ │ │ 4MB块 │ (FREE) │ │
│ │ │ order=10 │ │ │
│ │ └──────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ chunk_size = 4KB │
│ max_order = 21 │
│ size = 12MB │
│ avail = 8MB (可用) │
└─────────────────────────────────────────────────────┘
块的双重身份
每个块同时存在于两种数据结构中:
┌──────────────────────────────────────────┐
│ struct drm_buddy_block │
│ │
│ 身份1: 二叉树节点 │
│ ┌────────────────────────┐ │
│ │ parent ───→ 父节点 │ │
│ │ left ───→ 左子节点 │ │
│ │ right ───→ 右子节点 │ │
│ └────────────────────────┘ │
│ 用于: 合并操作 │
│ │
│ 身份2: 链表节点 │
│ ┌────────────────────────┐ │
│ │ link.prev ───→ 前一块 │ │
│ │ link.next ───→ 后一块 │ │
│ └────────────────────────┘ │
│ 用于: 快速查找空闲块 │
│ │
│ 核心数据: header │
│ ┌────────────────────────┐ │
│ │ offset (偏移地址) │ │
│ │ order (阶数) │ │
│ │ state (状态) │ │
│ │ clear (清零标志) │ │
│ └────────────────────────┘ │
└──────────────────────────────────────────┘
示例:12MB VRAM的完整结构
VRAM: 12MB = 8MB + 4MB
chunk_size: 4KB
max_order: 11 (4MB = 4KB * 2^10)
初始状态:
drm_buddy mm:
free_list[11] → [8MB块] → [4MB块] → NULL
free_list[10] → NULL
...
free_list[0] → NULL
roots[0] → [8MB块]
roots[1] → [4MB块]
avail = 12MB
分配4MB后:
drm_buddy mm:
free_list[11] → NULL
free_list[10] → [4MB块] → [4MB块] → NULL ← 8MB分裂成两个4MB
...
roots[0] → [8MB块,SPLIT]
/ \
[4MB,ALLOCATED] [4MB,FREE]
roots[1] → [4MB块,FREE]
avail = 8MB
已分配的4MB块:
- header = 0x0000000000000A0A (offset=0, order=10, state=ALLOCATED)
- parent = roots[0]
- left = NULL
- right = NULL
- link: 不在链表中(已分配)
💡 重点提示
-
header的精妙设计: 用一个u64压缩多个字段,节省内存且提高效率。
-
双重数据结构: 树用于合并,链表用于查找,各司其职。
-
状态机: 块的三种状态(FREE/ALLOCATED/SPLIT)和严格的转换规则。
-
多根设计: 支持非2的幂次大小的内存,更灵活实用。
-
slab缓存: 块结构频繁分配释放,使用slab优化性能。
-
清零标志: 平衡安全性和性能,避免重复清零。
⚠️ 常见陷阱
❌ 陷阱1: "直接修改header字段"
- ✅ 正确: 使用mark_*函数修改状态,确保一致性。
❌ 陷阱2: "忘记更新avail统计"
- ✅ 正确: 分配和释放时同步更新mm->avail。
❌ 陷阱3: "混淆块的两个链表"
- ✅ 正确: link是空闲链表或用户链表,tmp_link是内部临时用途。
❌ 陷阱4: "认为只有一棵二叉树"
- ✅ 正确: 可能有多个根(n_roots > 1),需要遍历roots数组。
📝 实践练习
-
计算header值:
c// 给定: offset=0x100000, order=5, state=FREE, clear=true // 计算header的值 u64 header = 0x100000; // offset header |= 5; // order header |= DRM_BUDDY_FREE; // state header |= DRM_BUDDY_HEADER_CLEAR; // clear flag // 验证提取 offset = drm_buddy_block_offset(block); // 0x100000 order = drm_buddy_block_order(block); // 5 state = drm_buddy_block_state(block); // FREE is_clear = drm_buddy_block_is_clear(block); // true -
设计内存布局:
给定: VRAM大小 = 6MB, chunk_size = 4KB 问题: a) 需要多少个根节点?(提示: 6MB = 4MB + 2MB) b) 每个根的order是多少? c) 画出roots数组和对应的树结构 d) max_order应该设置为多少? -
追踪块状态变化:
场景: 分配8KB,从16KB块分裂 初始: - 16KB块: state=FREE, order=2, 在free_list[2] 步骤1: 分裂16KB → 两个8KB - 16KB块: state=? left=? right=? 在哪个链表? - 左8KB块: state=? order=? 在哪个链表? - 右8KB块: state=? order=? 在哪个链表? 步骤2: 分配左8KB块 - 左8KB块: state=? 在哪个链表? - 右8KB块: state不变 填写每一步的状态
📚 本章小结
- drm_buddy: 管理整个分配器,维护free_list和roots
- drm_buddy_block: 内存块节点,同时是树节点和链表节点
- header字段: 压缩存储offset、order、state、clear标志
- 三种状态: FREE(空闲)、ALLOCATED(已分配)、SPLIT(已分裂)
- 双重结构: 二叉树负责合并,链表负责查找
- 多根设计: 支持非2的幂次大小的内存管理
理解了数据结构后,下一章将学习这些结构如何在实际代码中使用。
➡️ 下一步
掌握了核心数据结构后,下一章将介绍AMDGPU如何集成和使用这些数据结构。
👉 [05-AMDGPU中的VRAM管理器]审核中...