版本:基于 libdrm amdgpu 用户态驱动(amdgpu_vamgr.c
文件位置amdgpu/amdgpu_vamgr.camdgpu/amdgpu_internal.h


目录

  1. 背景与动机
  2. 数据结构设计
  3. 地址空间分区模型
  4. 初始化与销毁
  5. VA 分配算法
  6. VA 释放与合并算法
  7. 公共 API
  8. 线程安全模型
  9. 用户态与内核态职责划分
  10. 与 IGT amd_vm 测试的关联
  11. 总结

1. 背景与动机

1.1 为什么需要 GPU 虚拟地址空间?

现代 GPU 使用 IOMMU / GPU MMU 对显存和系统内存进行地址翻译。应用程序(以及内核 DRM 驱动)需要为每个 GPU Buffer Object (BO) 分配一段 GPU 虚拟地址(VA),再将该 VA 映射到物理页面,GPU 着色器才能通过 VA 访问数据。

GPU 执行单元
    │
    │  GPU VA (64-bit 虚拟地址)
    ▼
GPU MMU (页表硬件)
    │
    │  物理地址翻译
    ▼
DDR / VRAM 物理页面

1.2 为什么在用户态管理 VA?

方案 优点 缺点
每次分配都陷入内核 安全、内核统一管理 系统调用开销大,频繁分配场景性能差
用户态预分配 VA 池 零系统调用,亚微秒延迟 需要用户态自行管理碎片和并发

libdrm amdgpu 采用用户态 VA 管理:设备初始化时向内核查询可用的 VA 地址范围(virtual_address_offset / virtual_address_max),然后在用户空间维护一个空洞链表(free-hole list),应用程序分配 BO 时直接从链表中划取 VA,无需每次陷入内核。


2. 数据结构设计

2.1 层次关系

amdgpu_device
└── amdgpu_va_manager          ← 顶层 VA 管理器(每设备一个)
    ├── amdgpu_bo_va_mgr  vamgr_32        ← 低地址 32-bit 区域
    ├── amdgpu_bo_va_mgr  vamgr_low       ← 低地址 64-bit 区域
    ├── amdgpu_bo_va_mgr  vamgr_high_32   ← 高地址 32-bit 区域
    └── amdgpu_bo_va_mgr  vamgr_high      ← 高地址 64-bit 区域
         └── va_holes: list_head          ← 空洞链表(降序排列)
              ├── amdgpu_bo_va_hole { offset=0xC000, size=0x4000 }
              ├── amdgpu_bo_va_hole { offset=0x8000, size=0x1000 }
              └── amdgpu_bo_va_hole { offset=0x0000, size=0x3000 }

2.2 结构体定义

struct amdgpu_bo_va_hole — 空闲 VA 块
struct amdgpu_bo_va_hole {
    struct list_head list;   // 链表节点(按 offset 降序排列)
    uint64_t offset;         // 空洞起始虚拟地址
    uint64_t size;           // 空洞大小(字节)
};

不变量:链表按 offset 降序排列(高地址在前,低地址在后)。
这使得从低地址分配时只需反向遍历(LIST_FOR_EACH_ENTRY_SAFE_REV),效率高。

struct amdgpu_bo_va_mgr — 单区域子管理器
struct amdgpu_bo_va_mgr {
    uint64_t        va_max;        // 本区域的地址上限
    struct list_head va_holes;     // 空洞链表(降序)
    pthread_mutex_t bo_va_mutex;   // 保护 va_holes 的互斥锁
    uint32_t        va_alignment;  // 强制地址对齐粒度
};
struct amdgpu_va — 已分配 VA 句柄
struct amdgpu_va {
    uint64_t               address;  // 分配到的 GPU 虚拟地址
    uint64_t               size;     // 大小(已对齐到 va_alignment)
    enum amdgpu_gpu_va_range range;  // 范围类型(当前只有 general=0)
    struct amdgpu_bo_va_mgr *vamgr;  // 归属哪个子管理器(用于释放)
};
struct amdgpu_va_manager — 顶层管理器
struct amdgpu_va_manager {
    struct amdgpu_bo_va_mgr vamgr_low;      // 低地址 64-bit 区
    struct amdgpu_bo_va_mgr vamgr_32;       // 低地址 32-bit 区
    struct amdgpu_bo_va_mgr vamgr_high;     // 高地址 64-bit 区
    struct amdgpu_bo_va_mgr vamgr_high_32;  // 高地址 32-bit 区
};

3. 地址空间分区模型

3.1 分区逻辑

amdgpu_va_manager_init() 根据内核上报的 low_va_offsetlow_va_maxhigh_va_offsethigh_va_max 将地址空间切分为四个子区域:

低地址空间 (low VA space):
┌─────────────────────────────────────────────────────┐
│ vamgr_32  [low_va_offset, min(low_va_max, 4GB))     │  ← 可被 32-bit 指针寻址
├─────────────────────────────────────────────────────┤
│ vamgr_low [4GB,           low_va_max)               │  ← 64-bit 低地址区
└─────────────────────────────────────────────────────┘

高地址空间 (high VA space):
┌─────────────────────────────────────────────────────┐
│ vamgr_high_32 [high_va_offset, high_va_offset+4GB)  │  ← 高区内 32-bit 偏移可寻址
├─────────────────────────────────────────────────────┤
│ vamgr_high    [high_va_offset+4GB, high_va_max)     │  ← 高区 64-bit 区
└─────────────────────────────────────────────────────┘

对应代码

// vamgr_32:低地址空间中 4GB 以下的部分
start = low_va_offset;
max   = MIN2(low_va_max, 0x100000000ULL);          // 上限截至 4GB
amdgpu_vamgr_init(&va_mgr->vamgr_32, start, max, alignment);

// vamgr_low:低地址空间中 4GB 以上的部分
start = max;
max   = MAX2(low_va_max, 0x100000000ULL);
amdgpu_vamgr_init(&va_mgr->vamgr_low, start, max, alignment);

// vamgr_high_32:高地址空间内偏移 4GB 以下的部分
start = high_va_offset;
max   = MIN2(high_va_max, (start & ~0xffffffffULL) + 0x100000000ULL);
amdgpu_vamgr_init(&va_mgr->vamgr_high_32, start, max, alignment);

// vamgr_high:高地址空间内偏移 4GB 以上的部分
start = max;
max   = MAX2(high_va_max, (start & ~0xffffffffULL) + 0x100000000ULL);
amdgpu_vamgr_init(&va_mgr->vamgr_high, start, max, alignment);

3.2 子管理器选择逻辑(分配时)

分配标志位(flags)决定使用哪个子管理器:

flags 组合 选择的子管理器 说明
0(无标志) vamgr_low 默认:低地址 64-bit 区
AMDGPU_VA_RANGE_32_BIT vamgr_32 强制 32-bit 可寻址
AMDGPU_VA_RANGE_HIGH vamgr_high 高地址 64-bit 区
HIGH | 32_BIT vamgr_high_32 高地址 32-bit 区
AMDGPU_VA_RANGE_REPLAYABLE (任意)+ search_from_top=true 可重放命令流专用,从高地址向下分配

降级回退:若非 32-bit 请求分配失败,自动尝试对应的 32-bit 区域。


4. 初始化与销毁

4.1 子管理器初始化 amdgpu_vamgr_init()

amdgpu_vamgr_init(mgr, start, max, alignment)
│
├── 设置 mgr->va_max = max
├── 设置 mgr->va_alignment = alignment
├── 初始化空洞链表 list_inithead(&mgr->va_holes)
├── 初始化互斥锁 pthread_mutex_init(...)
└── 创建初始空洞 hole = {offset=start, size=max-start}
    并插入链表(此时链表仅一个节点,覆盖整个区域)

初始状态:链表中只有一个 “big bang” 空洞,覆盖整个地址区间。

4.2 销毁 amdgpu_vamgr_deinit()

遍历链表,逐个 free() 所有空洞节点,最后销毁互斥锁。


5. VA 分配算法

5.1 入口:amdgpu_vamgr_find_va()

函数签名

static int amdgpu_vamgr_find_va(
    struct amdgpu_bo_va_mgr *mgr,
    uint64_t size,           // 请求大小
    uint64_t alignment,      // 对齐要求
    uint64_t base_required,  // 强制指定地址(0 = 任意)
    bool search_from_top,    // true = 从高地址分配(REPLAYABLE)
    uint64_t *va_out         // 输出:分配到的 VA
);

5.2 分配流程

请求到来
│
├── 对齐处理
│   alignment = MAX2(alignment, mgr->va_alignment)
│   size      = ALIGN(size, mgr->va_alignment)
│
├── 检查 base_required % alignment == 0,否则返回 -EINVAL
│
├── 加锁 pthread_mutex_lock
│
├── search_from_top == false?(正常模式,从低地址分配)
│   └── LIST_FOR_EACH_ENTRY_SAFE_REV(反向遍历,低地址优先)
│       ├── base_required != 0:检查空洞是否包含 [base_required, base_required+size)
│       └── base_required == 0:
│           waste = 对齐 hole->offset 所需的填充字节
│           offset = hole->offset + waste
│           检查 offset + size <= hole->offset + hole->size
│
└── search_from_top == true?(REPLAYABLE,从高地址分配)
    └── LIST_FOR_EACH_ENTRY_SAFE(正向遍历,高地址优先)
        ├── base_required != 0:同上
        └── base_required == 0:
            offset = hole->offset + hole->size - size  (尽量靠近空洞顶端)
            offset -= offset % alignment               (向下对齐)
            检查 offset >= hole->offset
│
找到合适空洞后:
└── 调用 amdgpu_vamgr_subtract_hole(hole, offset, offset+size)
    解锁,返回 offset

5.3 空洞分割:amdgpu_vamgr_subtract_hole()

从空洞中"挖去"已分配范围 [start_va, end_va),共有四种情形:

情形 1:分割中间(两侧都有剩余)
  原空洞: [  offset  .............  offset+size  )
  分配区: [    start_va  ...  end_va    )
  结果:   [offset, start_va) + [end_va, offset+size)
  → 原节点缩小为右半段,新建节点插入左半段

  before: ┌────────────────────────────┐
          │        hole                │
          └────────────────────────────┘
  after:  ┌──────────┐  ████████  ┌───┐
          │  新 hole │  已分配     │old│
          └──────────┘            └───┘

情形 2:trim 右侧(start_va > offset,但 end_va == offset+size)
  → hole->size = start_va - offset

情形 3:trim 左侧(start_va == offset,但 end_va < offset+size)
  → hole->offset = end_va; hole->size -= (end_va - offset)

情形 4:整个空洞被消耗(start_va==offset && end_va==offset+size)
  → list_del + free(hole)

判断条件汇总

条件 情形
start_va > offset end_va - offset < size 中间分割(情形 1)
start_va > offset trim 右(情形 2)
end_va - offset < size trim 左(情形 3)
否则 全部消耗(情形 4)

6. VA 释放与合并算法

6.1 amdgpu_vamgr_free_va()

amdgpu_vamgr_free_va(mgr, va, size)
│
├── 忽略无效地址 AMDGPU_INVALID_VA_ADDRESS (0xfff...f)
├── size = ALIGN(size, mgr->va_alignment)
├── 加锁
│
├── 在降序链表中定位插入位置:
│   找到第一个 next->offset < va 的节点
│   hole 指向其前驱(offset >= va 的最后一个节点)
│
├── 尝试向上合并(与 hole 合并):
│   if hole->offset == va + size:
│       hole->offset = va;  hole->size += size
│       尝试向下合并(hole 与 next 合并):
│           if next->offset + next->size == va(新 hole 起始):
│               next->size += hole->size;  删除 hole
│
├── 否则,尝试向下合并(仅与 next 合并):
│   if next->offset + next->size == va:
│       next->size += size
│
└── 否则,创建新空洞节点插入链表
    calloc(1, sizeof(amdgpu_bo_va_hole))
    next = {offset=va, size=size}
    list_add(&next->list, &hole->list)   ← 插在 hole 和 next 之间

6.2 合并示意图

场景:已有空洞 H1=[0x0000,0x2000) 和 H2=[0x6000,0x2000)
      现在释放 [0x2000, 0x4000)

链表(降序):H2(0x6000) → H1(0x0000)

步骤:
1. 找到 hole=H2(0x6000 > 0x2000),next=H1(0x0000 < 0x2000)
2. H2->offset(0x6000) ≠ va+size(0x6000)?  → 恰好等于!
   H2->offset = 0x2000; H2->size = 0x4000  (向上合并)
3. next=H1,(H1->offset + H1->size) = 0x2000 == va(0x2000)
   H1->size += H2->size = 0x6000  (向下合并)
   删除 H2

最终链表:H1=[0x0000, 0x6000)  ← 三段合并为一个大空洞

7. 公共 API

7.1 API 总览

函数 可见性 说明
amdgpu_va_range_alloc() drm_public 从设备 VA 管理器分配 VA
amdgpu_va_range_alloc2() drm_public 从独立的 VA manager 句柄分配(支持多 VM 实例)
amdgpu_va_range_free() drm_public 归还 VA 并释放句柄
amdgpu_va_range_query() drm_public 查询设备 VA 地址范围(start/end)
amdgpu_va_get_start_addr() drm_public 从句柄获取分配到的 VA
amdgpu_va_manager_alloc() drm_public 动态分配独立的 VA manager(用于多 VM)
amdgpu_va_manager_init() drm_public 初始化 VA manager(四个子区域)
amdgpu_va_manager_deinit() drm_public 销毁 VA manager
amdgpu_vamgr_init() drm_private 初始化单个子管理器(内部用)
amdgpu_vamgr_deinit() drm_private 销毁单个子管理器(内部用)

7.2 典型使用流程

/* 1. 打开设备(va_mgr 嵌入在 amdgpu_device 中,由 amdgpu_device_initialize 初始化) */
amdgpu_device_handle dev;
amdgpu_device_initialize(fd, &major, &minor, &dev);

/* 2. 查询 VA 范围(可选,用于了解当前硬件能力) */
uint64_t va_start, va_end;
amdgpu_va_range_query(dev, amdgpu_gpu_va_range_general, &va_start, &va_end);

/* 3. 分配一段 GPU 虚拟地址 */
amdgpu_va_handle va_handle;
uint64_t va_addr;
amdgpu_va_range_alloc(dev,
                       amdgpu_gpu_va_range_general,
                       size,           // 请求大小
                       alignment,      // 对齐(0=使用默认)
                       0,              // base_required(0=任意地址)
                       &va_addr,       // 输出:分配到的 VA
                       &va_handle,     // 输出:VA 句柄
                       0);             // flags(0=低地址 64-bit 区)

/* 4. 将 BO 映射到这个 VA(通过 amdgpu_bo_va_op) */
amdgpu_bo_va_op(bo, 0, size, va_addr, 0, AMDGPU_VA_OP_MAP);

/* 5. GPU 执行完后,解除映射并归还 VA */
amdgpu_bo_va_op(bo, 0, size, va_addr, 0, AMDGPU_VA_OP_UNMAP);
amdgpu_va_range_free(va_handle);

7.3 flags 标志位

#define AMDGPU_VA_RANGE_32_BIT     0x1  // 要求地址在 32-bit 可寻址范围内
#define AMDGPU_VA_RANGE_HIGH       0x2  // 从高 VA 地址空间分配(如 VM=1 的地址段)
#define AMDGPU_VA_RANGE_REPLAYABLE 0x4  // 可重放命令流专用,从高地址向下分配

8. 线程安全模型

8.1 锁粒度

每个 amdgpu_bo_va_mgr 有一个独立的 pthread_mutex_t bo_va_mutex,覆盖对 va_holes 链表的所有读写操作。

线程 A 分配 vamgr_low  ─────────────────────┐ 持有 vamgr_low.bo_va_mutex
线程 B 分配 vamgr_high ────────────────────┐│ 持有 vamgr_high.bo_va_mutex
线程 C 分配 vamgr_32   ───────────────────┐││ 持有 vamgr_32.bo_va_mutex
                                         ▼▼▼
                              三个线程完全并行,互不阻塞

优点:四个子管理器各自独立加锁,低地址和高地址的分配完全并发。

8.2 锁的获取与释放

  • amdgpu_vamgr_find_va():加锁 → 修改链表 → 解锁(成功/失败都解锁)
  • amdgpu_vamgr_free_va():加锁 → 修改链表 → goto out 解锁
  • amdgpu_vamgr_init():初始化时主动加锁插入初始节点,完成后解锁

9. 用户态与内核态职责划分

┌─────────────────────────────────────────────────────────────┐
│                    用户态(libdrm amdgpu)                  │
│                                                             │
│  amdgpu_va_manager                                          │
│  ├── VA 地址范围划分(4个子区域)                            │
│  ├── 空洞链表管理(分配/释放/合并)                          │
│  ├── 对齐对齐处理                                            │
│  ├── 线程安全(pthread_mutex)                               │
│  └── VA 句柄(amdgpu_va)生命周期管理                        │
│                                                             │
│  仅管理"VA 号码",不涉及页表!                               │
└──────────────────────────┬──────────────────────────────────┘
                           │ ioctl: AMDGPU_GEM_VA
                           │   (DRM_AMDGPU_GEM_VA)
┌──────────────────────────▼──────────────────────────────────┐
│                    内核态(amdgpu KMD)                     │
│                                                             │
│  amdgpu_vm                                                  │
│  ├── 页表分配(PD/PT 物理页)                                │
│  ├── TLB 刷新                                               │
│  ├── VA → 物理地址映射写入硬件页表                          │
│  ├── VM 故障处理(page fault handler)                      │
│  └── IOMMU 配置                                             │
└─────────────────────────────────────────────────────────────┘

关键区别:用户态 VA manager 只负责"哪个 VA 号码已被占用",它不操作页表。真正建立 VA→物理地址映射是通过 amdgpu_bo_va_op() 触发 DRM_AMDGPU_GEM_VA ioctl 由内核完成。


10. 与 IGT amd_vm 测试的关联

IGT(Intel GPU Tools)的 tests/amdgpu/amd_vm.c 直接测试上述 VA 管理机制:

IGT 子测试 对应的 VA 管理操作
bo-map-test amdgpu_va_range_alloc + amdgpu_bo_va_op(MAP)
vmid-reserve-test 测试 VMID 资源预留,间接验证 VA 分配一致性
vm-mapping-test 全流程:分配 VA → 映射 → GPU 读写 → 解映射 → 释放 VA
large-bar-test 测试高地址(AMDGPU_VA_RANGE_HIGH)分配路径

调试建议:在 VS Code 中使用 Debug amd_vm 配置(sudo-gdb.sh 方案),在以下关键断点处检查 VA 管理状态:

amdgpu_va_range_alloc2    ← 观察子管理器选择逻辑
amdgpu_vamgr_find_va      ← 观察空洞搜索过程
amdgpu_vamgr_subtract_hole ← 观察空洞分割
amdgpu_vamgr_free_va      ← 观察合并逻辑

11. 总结

11.1 设计亮点

特性 实现方式 收益
零内核调用分配 用户态空洞链表 亚微秒级 VA 分配延迟
四区域分治 4个独立子管理器 支持 32/64-bit 及 low/high 差异化策略
细粒度锁 per-manager mutex 四个区域完全并发,减少锁争用
自动合并 free 时三向合并 避免 VA 碎片化
降级回退 64-bit 失败→32-bit 提高分配成功率
REPLAYABLE 支持 search_from_top=true 可重放命令流从高地址分配,避免与普通分配冲突

11.2 已知局限

  • VA 泄漏无报警amdgpu_vamgr_free_va()calloc 失败时只有 /* FIXME */ 注释,VA 会静默泄漏。
  • 无碎片整理:纯 first-fit 策略,长期运行后可能出现碎片,但 VA 空间(48-bit = 256TB)远大于实际使用量,实践中影响有限。
  • amdgpu_gpu_va_range 枚举只有一个值:当前 amdgpu_gpu_va_range_general=0 是唯一合法范围,flags 机制承担了更精细的区域选择职责。

11.3 整体数据流

应用程序
  │
  │  amdgpu_va_range_alloc(dev, size, align, 0, &va, &handle, flags)
  ▼
amdgpu_va_range_alloc2()
  ├── 根据 flags 选择 vamgr(4选1)
  ├── amdgpu_vamgr_find_va()   ← 从空洞链表找可用地址
  │     └── amdgpu_vamgr_subtract_hole()  ← 从空洞中裁出已分配段
  └── calloc amdgpu_va 句柄,记录 address/size/vamgr
  │
  │  返回 va(GPU 虚拟地址)和 handle
  ▼
应用程序调用 amdgpu_bo_va_op(bo, offset, size, va, 0, AMDGPU_VA_OP_MAP)
  │
  │  ioctl DRM_AMDGPU_GEM_VA
  ▼
内核 amdgpu KMD:建立页表映射,GPU 可访问该 VA
  │
  │  ... GPU 执行 ...
  ▼
应用程序调用 amdgpu_bo_va_op(..., AMDGPU_VA_OP_UNMAP)
  │
  ▼
应用程序调用 amdgpu_va_range_free(handle)
  └── amdgpu_vamgr_free_va()  ← 归还 VA,合并相邻空洞
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐