1. 概述
1.1 TLB在GPUVM中的作用
TLB(Translation Lookaside Buffer)是GPU虚拟内存系统的性能关键,缓存虚拟地址到物理地址的映射关系。当页表更新后,必须刷新TLB以保证地址转换的正确性。
虚拟地址转换流程
┌─────────────────────────────────────────────┐
│ GPU访问VA │
│ ↓ │
│ TLB查询 ──命中──> 直接获取物理地址 │
│ ↓ │
│ 未命中 │
│ ↓ │
│ 页表walk(多级查) │
│ ↓ │
│ 更新TLB entry │
│ ↓ │
│ 访问物理地址 │
└─────────────────────────────────────────────┘
1.2 TLB刷新的必要性
问题场景:
T1: 页表PTE[0x100000] = 物理地址A
T2: TLB缓存: VA 0x100000 → 物理地址A
T3: 更新PTE[0x100000] = 物理地址B
T4: GPU访问0x100000 → TLB仍返回地址A ❌ (错误!)
解决方案:T3后立即刷新TLB,清除过期映射。
1.3 AMDGPU TLB架构
GPU TLB层次结构
┌────────────────────────────────────────────────────┐
│ VMHUB 0 (GFX) VMHUB 1 (Compute/SDMA) │
│ ┌──────────┐ ┌──────────┐ │
│ │ L1 TLB │ │ L1 TLB │ │
│ │ (每个CU) │ │ (每个CU) │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ v v │
│ ┌──────────┐ ┌──────────┐ │
│ │ L2 TLB │ │ L2 TLB │ │
│ │ (共享) │ │ (共享) │ │
│ └──────────┘ └──────────┘ │
└────────────────────────────────────────────────────┘
关键特性:
- 多级TLB:L1(小而快,每CU独立)+ L2(大而共享)
- 多VMHUB:每个hub独立管理TLB
- VMID关联:TLB entry绑定VMID
2. TLB刷新时机
2.1 四大触发场景
2.1.1 页表更新后
c
/* 场景1:BO映射变化 */
amdgpu_vm_bo_update(adev, bo_va, clear)
→ amdgpu_vm_update_range()
→ 写入新PTE
→ if (flush_tlb)
amdgpu_vm_tlb_flush() // 标记需要刷新
/* 场景2:BO迁移 */
amdgpu_vm_handle_moved()
→ amdgpu_vm_bo_update()
→ 更新PTE指向新物理地址
→ 标记vm->tlb_seq++
判断逻辑:
c
if (vm->last_update_op != current_op)
flush_tlb = true;
2.1.2 VMID切换时
c
/* 不同VM使用同一VMID */
amdgpu_vmid_grab()
→ 分配VMID给新VM
→ job->vm_needs_flush = true; // 强制刷新
原因:VMID重用时,TLB中可能残留旧VM的映射
2.1.3 GPU复位后
c
if (amdgpu_vmid_had_gpu_reset(adev, id)) {
vm_flush_needed = true;
pasid_mapping_needed = true;
}
原因:GPU复位导致所有硬件状态丢失
2.1.4 显式flush请求
c
/* Compute VM强制flush */
amdgpu_vm_flush_compute_tlb(adev, vm, flush_type, xcc_mask)
2.2 刷新触发流程图
触发源 检测机制 执行时机
┌────────────┐ ┌─────────────┐ ┌────────────┐
│ 页表更新 │ ────> │ tlb_seq++ │ ────> │ CS提交时 │
└────────────┘ └─────────────┘ │ flush执行 │
└────────────┘
┌────────────┐ ┌──────────────┐ │
│ VMID切换 │ ────> │vm_needs_flush │ ───────────────┘
└────────────┘ └──────────────┘
┌────────────┐ ┌─────────────┐ ┌────────────┐
│ GPU复位 │ ────> │强制刷新 │ ──── > │ 立即执行 │
└────────────┘ └─────────────┘ └────────────┘
3. TLB序列号机制
3.1 核心数据结构
位置 :drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h
c
struct amdgpu_vm {
atomic64_t tlb_seq; // TLB序列号(递增)
struct dma_fence *last_tlb_flush; // 上次flush的fence
atomic64_t kfd_last_flushed_seq; // KFD/Compute专用
};
struct amdgpu_job {
uint64_t vm_tlb_seq; // 快照:job创建时的seq
bool vm_needs_flush; // 是否需要flush
};
3.2 序列号工作原理
时间线:
T0: vm->tlb_seq = 100
↓
T1: 更新页表(amdgpu_vm_update_range)
↓
T2: atomic64_inc(&vm->tlb_seq) → seq = 101
↓
T3: 创建CS job
job->vm_tlb_seq = 100(快照旧值)
↓
T4: amdgpu_vm_flush()检查
if (job->vm_tlb_seq != vm->tlb_seq) // 100 != 101
job->vm_needs_flush = true;
↓
T5: emit TLB invalidate命令
↓
T6: fence signal后,job->vm_tlb_seq更新为101
3.3 序列号递增时机
方式1:通过fence callback
c
/* 页表更新完成后递增 */
static void amdgpu_vm_tlb_seq_cb(struct dma_fence *fence, ...) {
struct amdgpu_vm *vm = ...;
atomic64_inc(&vm->tlb_seq); // 版本+1
}
/* 绑定callback */
amdgpu_vm_update_range()
→ vm->update_funcs->commit(¶ms, fence)
→ dma_fence_add_callback(fence, tlb_seq_cb)
方式2:CPU模式立即递增
c
amdgpu_vm_cpu_commit() {
if (p->needs_flush)
atomic64_inc(&p->vm->tlb_seq); // 同步更新
mb(); // 内存屏障
}
3.4 Compute VM的特殊处理
c
int amdgpu_vm_flush_compute_tlb(...) {
uint64_t tlb_seq = amdgpu_vm_tlb_seq(vm);
// 使用xchg原子操作避免重复flush
if (atomic64_xchg(&vm->kfd_last_flushed_seq, tlb_seq) == tlb_seq)
return 0; // 已刷新过,跳过
// 执行PASID flush
amdgpu_gmc_flush_gpu_tlb_pasid(adev, vm->pasid, ...);
}
优势:多个KFD队列共享VM时,避免重复flush
4. Graphics模式flush:amdgpu_vm_flush()
4.1 函数概览
位置 :drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
调用时机:CS提交时,在IB执行前
c
int amdgpu_vm_flush(struct amdgpu_ring *ring,
struct amdgpu_job *job,
bool need_pipe_sync)
{
bool vm_flush_needed = job->vm_needs_flush;
bool pasid_mapping_needed = false;
// 1. 检查GPU reset
if (amdgpu_vmid_had_gpu_reset(adev, id)) {
vm_flush_needed = true;
pasid_mapping_needed = true;
}
// 2. 检查PASID映射
if (id->pasid != job->pasid || !id->pasid_mapping)
pasid_mapping_needed = true;
// 3. 快速退出
if (!vm_flush_needed && !need_pipe_sync)
return 0;
// 4. 构造命令
amdgpu_ring_ib_begin(ring);
if (need_pipe_sync)
amdgpu_ring_emit_pipeline_sync(ring);
if (vm_flush_needed) {
trace_amdgpu_vm_flush(ring, job->vmid, job->vm_pd_addr);
amdgpu_ring_emit_vm_flush(ring, job->vmid, job->vm_pd_addr);
}
if (pasid_mapping_needed)
amdgpu_gmc_emit_pasid_mapping(ring, job->vmid, job->pasid);
// 5. 发出fence
amdgpu_fence_emit(ring, &fence, NULL, 0);
// 6. 更新VMID状态
id->last_flush = dma_fence_get(fence);
id->pasid_mapping = dma_fence_get(fence);
amdgpu_ring_ib_end(ring);
}
4.2 关键步骤详解
4.2.1 条件判断
c
/* 需要flush的情况 */
bool vm_flush_needed =
job->vm_needs_flush && // tlb_seq变化
ring->funcs->emit_vm_flush && // 硬件支持
job->vm_pd_addr != INVALID; // 有效PD地址
/* 需要PASID映射的情况 */
bool pasid_mapping_needed =
(id->pasid != job->pasid) || // PASID变化
!id->pasid_mapping || // 首次映射
!dma_fence_is_signaled(...); // 上次映射未完成
4.2.2 Pipeline Sync
c
if (need_pipe_sync)
amdgpu_ring_emit_pipeline_sync(ring);
作用:等待所有之前的命令执行完成,确保TLB flush在正确时机生效
4.2.3 TLB Invalidate命令
c
amdgpu_ring_emit_vm_flush(ring, job->vmid, job->vm_pd_addr);
硬件实现(例如GFX9):
c
/* PM4命令包 */
PACKET3(PACKET3_INVALIDATE_TLBS, 0)
VMID(job->vmid) // 指定VMID
FLUSH_TYPE(0) // 0=全部, 2=PDE/PTE
4.2.4 PASID映射更新
c
amdgpu_gmc_emit_pasid_mapping(ring, job->vmid, job->pasid);
作用:更新VMID→PASID的硬件映射表,用于ATS/IOMMU
4.3 Fence同步机制
c
/* 发出fence */
amdgpu_fence_emit(ring, &fence, NULL, 0);
/* 更新VMID状态 */
mutex_lock(&id_mgr->lock);
dma_fence_put(id->last_flush);
id->last_flush = dma_fence_get(fence); // 记录最新flush fence
mutex_unlock(&id_mgr->lock);
用途:
- 其他job可以等待此fence,确保TLB已刷新
- 避免并发访问过期TLB entry
5. Compute模式flush:amdgpu_vm_flush_compute_tlb()
5.1 与Graphics模式的区别
| 特性 | Graphics模式 | Compute模式 |
|---|---|---|
| 触发方式 | CS提交时隐式 | KFD显式调用 |
| 使用标识 | VMID | PASID |
| flush范围 | 单个VMHUB | 可选all_hub |
| 去重机制 | 无 | kfd_last_flushed_seq |
5.2 核心实现
位置 :drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
c
int amdgpu_vm_flush_compute_tlb(struct amdgpu_device *adev,
struct amdgpu_vm *vm,
uint32_t flush_type,
uint32_t xcc_mask)
{
uint64_t tlb_seq = amdgpu_vm_tlb_seq(vm);
// 1. 去重检查
if (atomic64_xchg(&vm->kfd_last_flushed_seq, tlb_seq) == tlb_seq)
return 0; // 已flush过此版本
// 2. 特定架构flush所有hub
bool all_hub = false;
if (adev->family == AMDGPU_FAMILY_AI ||
adev->family == AMDGPU_FAMILY_RV)
all_hub = true;
// 3. 遍历XCC执行flush
for_each_inst(xcc, xcc_mask) {
r = amdgpu_gmc_flush_gpu_tlb_pasid(adev, vm->pasid,
flush_type, all_hub, xcc);
if (r) break;
}
}
5.3 PASID flush实现
位置 :drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c
c
int amdgpu_gmc_flush_gpu_tlb_pasid(adev, pasid, flush_type, all_hub, inst)
{
struct amdgpu_ring *ring = &adev->gfx.kiq[inst].ring;
// 方式1:使用KIQ(内核接口队列)
if (adev->gmc.flush_pasid_uses_kiq && ring->sched.ready) {
// 构造KIQ命令包
amdgpu_ring_alloc(ring, ndw);
kiq->pmf->kiq_invalidate_tlbs(ring, pasid, flush_type, all_hub);
amdgpu_ring_commit(ring);
// 等待完成
r = amdgpu_fence_wait_polling(ring, seq, timeout);
}
// 方式2:直接寄存器访问
else {
adev->gmc.gmc_funcs->flush_gpu_tlb_pasid(adev, pasid,
flush_type, all_hub, inst);
}
}
KIQ优势:
- 异步执行,不阻塞CPU
- 多XCC并行flush
6. 硬件flush实现
6.1 VMID flush(Graphics)
位置 :drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c(示例)
c
static void gmc_v9_0_flush_gpu_tlb(adev, vmid, vmhub, flush_type) {
// 1. 选择寄存器地址
uint32_t reg = hub->vm_inv_eng0_req + hub->eng_distance * eng;
// 2. 写入flush请求
WREG32_NO_KIQ(reg,
INVALIDATE_L2_PTES(1) |
INVALIDATE_L2_PDE0(1) |
INVALIDATE_L1_PTES(1) |
CLEAR_PROTECTION_FAULT_STATUS_ADDR(0) |
VMID(vmid) |
FLUSH_TYPE(flush_type));
// 3. 轮询ACK位
for (i = 0; i < timeout; i++) {
if (RREG32_NO_KIQ(ack_reg) & (1 << vmid))
break;
udelay(1);
}
}
关键参数:
- INVALIDATE_L2_PTES:清除L2 TLB中的PTE
- INVALIDATE_L2_PDE0:清除L2 TLB中的PDE
- INVALIDATE_L1_PTES:清除L1 TLB
- FLUSH_TYPE:0=全部, 2=仅PDE/PTE
6.2 PASID flush(Compute)
c
static void gmc_v9_0_flush_gpu_tlb_pasid(adev, pasid, flush_type, all_hub, inst) {
uint32_t seq;
uint16_t queried_pasid;
// 1. 写入PASID和flush类型
WREG32_SOC15(GC, 0, mmVM_INVALIDATE_ENG17_REQ,
PASID(pasid) |
FLUSH_TYPE(flush_type) |
INVALIDATE_L2_PTES(1) |
INVALIDATE_L2_PDE0(1) |
INVALIDATE_L1_PTES(1));
// 2. 读取ACK
for (i = 0; i < timeout; i++) {
seq = RREG32_SOC15(GC, 0, mmVM_INVALIDATE_ENG17_ACK);
if (seq & 0x80000000) // 检查busy位
break;
udelay(1);
}
}
6.3 Flush类型说明
c
/* flush_type定义 */
#define AMDGPU_TLB_FLUSH_LEGACY 0 // 全部flush(包括L1/L2)
#define AMDGPU_TLB_FLUSH_LIGHT 2 // 仅PDE/PTE(不清L1)
/* 使用场景 */
- Legacy: VMID切换、GPU reset
- Light: 页表更新(频繁场景)
7. 延迟flush优化
7.1 批量flush策略
传统方式(每次更新立即flush):
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ Map │──>│Flush│ │ Map │──>│Flush│ ... (N次flush)
└─────┘ └─────┘ └─────┘ └─────┘
优化方式(批量flush):
┌─────┐ ┌─────┐ ┌─────┐ ┌───────┐
│ Map │ │ Map │ │ Map │──>│ Flush │ (1次flush)
└─────┘ └─────┘ └─────┘ └───────┘
tlb_seq++ CS时执行
7.2 序列号机制优势
c
/* 多次更新只flush一次 */
T1: amdgpu_vm_bo_update() → tlb_seq: 100 → 101
T2: amdgpu_vm_bo_update() → tlb_seq: 101 → 102
T3: amdgpu_vm_bo_update() → tlb_seq: 102 → 103
T4: CS提交
job->vm_tlb_seq = 100(创建时快照)
current tlb_seq = 103
→ 只需flush一次,覆盖所有更新
7.3 Selective flush
c
/* 仅flush变化的VMID */
if (job->vm_pd_addr != id->last_pd_addr)
vm_flush_needed = true; // PD变化才flush
else
vm_flush_needed = false; // PD未变,TLB仍有效
8. 典型场景分析
8.1 场景1:普通BO映射
用户态:ioctl(MAP_BO)
↓
内核态:amdgpu_vm_bo_map()
↓
创建mapping,加入invalids
↓
CS提交:amdgpu_cs_vm_handling()
↓
amdgpu_vm_handle_moved()
└─ amdgpu_vm_bo_update()
└─ amdgpu_vm_update_range()
├─ 写入PTE
└─ tlb_seq: 100 → 101
↓
amdgpu_vm_flush()
├─ 检测: job->vm_tlb_seq(100) != vm->tlb_seq(101)
├─ emit_vm_flush(vmid, pd_addr)
└─ id->last_flush = fence
↓
GPU执行IB前flush TLB
8.2 场景2:BO驱逐恢复
BO被驱逐(VRAM→GTT)
↓
amdgpu_vm_bo_evicted() → 加入evicted链表
↓
下次CS提交:
├─ amdgpu_vm_validate()
│ └─ 恢复BO,转moved状态
├─ amdgpu_vm_handle_moved()
│ └─ 更新PTE指向GTT地址
│ └─ tlb_seq++
└─ amdgpu_vm_flush()
└─ 清除旧VRAM地址的TLB entry
↓
GPU访问新GTT地址
8.3 场景3:Compute队列提交
KFD用户队列提交
↓
amdgpu_amdkfd_gpuvm_restore_process_bos()
↓
检查VM状态
↓
if (页表已更新)
amdgpu_vm_flush_compute_tlb(vm, LEGACY, xcc_mask)
├─ 检查kfd_last_flushed_seq
├─ amdgpu_gmc_flush_gpu_tlb_pasid(pasid)
│ └─ KIQ发送invalidate命令
└─ 更新kfd_last_flushed_seq
↓
队列开始执行,TLB已刷新
9. 性能优化技巧
9.1 减少flush频率
c
/* 技巧1:延迟更新 */
多次map操作 → 一次flush
/* 技巧2:合并更新 */
amdgpu_vm_update_range(start=0, last=1000) // 一次更新1000页
→ 一次flush覆盖所有
/* 技巧3:避免不必要的flush */
if (job->vm_pd_addr == id->last_pd_addr &&
job->vm_tlb_seq == vm->tlb_seq)
skip_flush(); // PD和TLB版本均未变
9.2 使用Light flush
c
/* 频繁场景使用轻量flush */
if (adev->gmc.flush_tlb_needs_extra_type_2)
flush_type = AMDGPU_TLB_FLUSH_LIGHT; // type=2
else
flush_type = AMDGPU_TLB_FLUSH_LEGACY; // type=0
// Light flush: 仅清PDE/PTE,保留L1 TLB
// 适用于只修改部分映射的场景
9.3 异步flush
c
/* KIQ异步flush */
amdgpu_gmc_flush_gpu_tlb_pasid()
→ kiq_invalidate_tlbs() // 不阻塞CPU
→ 提交命令到KIQ ring
→ 轮询完成(polling模式)
// 优势:CPU可继续其他工作
// 劣势:需要轮询或fence等待
10. 调试与诊断
10.1 Tracepoint
bash
# 启用TLB flush tracing
echo 1 > /sys/kernel/debug/tracing/events/amdgpu/amdgpu_vm_flush/enable
# 查看输出
cat /sys/kernel/debug/tracing/trace
# 示例输出:
amdgpu_vm_flush: ring=gfx vmid=3 pd_addr=0x12345000
10.2 常见问题
问题1:TLB flush未生效
- 检查
vm->tlb_seq是否递增 - 确认
job->vm_needs_flush是否为true - 验证硬件ACK位是否返回
问题2:过度flush导致性能下降
- 检查是否频繁触发VMID切换
- 确认延迟flush机制是否生效
- 考虑增大VMID池大小
问题3:Compute flush失败
- 检查KIQ ring是否ready
- 确认PASID是否正确映射
- 验证
kfd_last_flushed_seq更新
10.3 性能分析
c
/* 统计flush次数和耗时 */
ktime_t start = ktime_get();
amdgpu_vm_flush(ring, job, false);
ktime_t end = ktime_get();
pr_info("TLB flush took %lld us, seq=%llu\n",
ktime_us_delta(end, start),
atomic64_read(&vm->tlb_seq));
11. 总结
11.1 核心要点
-
TLB架构:
- 多级TLB(L1/L2)
- 多VMHUB独立管理
- VMID/PASID双重标识
-
刷新时机:
- 页表更新后(tlb_seq机制)
- VMID切换时
- GPU复位后
- Compute显式请求
-
两种模式:
- Graphics:VMID flush,CS时隐式
- Compute:PASID flush,KFD显式
-
性能优化:
- 延迟flush(批量处理)
- Selective flush(仅变化部分)
- Light flush(type=2)
- 异步执行(KIQ)
11.2 关键数据流
页表更新 → tlb_seq++ → CS创建job(快照seq) →
vm_flush检测差异 → emit invalidate命令 →
硬件清除TLB → fence signal → 更新last_flush