04 - 核心数据结构详解

难度 : 🟡 进阶级
预计学习时间 : 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: 不在链表中(已分配)

💡 重点提示

  1. header的精妙设计: 用一个u64压缩多个字段,节省内存且提高效率。

  2. 双重数据结构: 树用于合并,链表用于查找,各司其职。

  3. 状态机: 块的三种状态(FREE/ALLOCATED/SPLIT)和严格的转换规则。

  4. 多根设计: 支持非2的幂次大小的内存,更灵活实用。

  5. slab缓存: 块结构频繁分配释放,使用slab优化性能。

  6. 清零标志: 平衡安全性和性能,避免重复清零。


⚠️ 常见陷阱

陷阱1: "直接修改header字段"

  • ✅ 正确: 使用mark_*函数修改状态,确保一致性。

陷阱2: "忘记更新avail统计"

  • ✅ 正确: 分配和释放时同步更新mm->avail。

陷阱3: "混淆块的两个链表"

  • ✅ 正确: link是空闲链表或用户链表,tmp_link是内部临时用途。

陷阱4: "认为只有一棵二叉树"

  • ✅ 正确: 可能有多个根(n_roots > 1),需要遍历roots数组。

📝 实践练习

  1. 计算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
  2. 设计内存布局

    复制代码
    给定: VRAM大小 = 6MB, chunk_size = 4KB
    
    问题:
    a) 需要多少个根节点?(提示: 6MB = 4MB + 2MB)
    b) 每个根的order是多少?
    c) 画出roots数组和对应的树结构
    d) max_order应该设置为多少?
  3. 追踪块状态变化

    复制代码
    场景: 分配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管理器]审核中...


相关推荐
DeeplyMind2 天前
03 - DRM子系统与AMDGPU架构
drm·drm_buddy·vram分配
DeeplyMind3 个月前
Linux DRM 内存管理子系统的概念关系理解:gem、ttm、drm_buddy
drm·tm·drm_buddy
DeeplyMind3 个月前
第7章:DRM内核调试技术:7.1 DRM DebugFS的使用
linux·驱动开发·drm·debugfs·drm debugfs
DeeplyMind4 个月前
linux drm子系统技术分析目录表
linux·驱动开发·drm
Micro麦可乐4 个月前
前端真的能防录屏?EME(加密媒体扩展) DRM 反录屏原理 + 实战代码
前端·媒体·eme·drm·前端防盗录
JasonSJX5 个月前
海海软件成为微软 PlayReady DRM 官方合作伙伴
microsoft·drm·视频加密·playready·数字版权保护
DeeplyMind5 个月前
linux drm子系统专栏介绍
linux·驱动开发·ai·drm·amdgpu·kfd
DeeplyMind7 个月前
AMD KFD的BO设计分析系列3-4:Linux DRM GEM mmap 与 drm_vma_offset_node 机制详解
linux·drm·opengl驱动·drm_gem_object
林政硕(Cohen0415)1 年前
Linux驱动开发进阶(七)- DRM驱动程序设计
linux·驱动开发·drm