第六章:异步访问的同步:6.1.3 dma_fence enable_signaling 深度解析
0. 引言
本节我们重点关注dma_fence.ops.enable_signaling的设计哲学和用法。我们先来看下内核中对该回调的注释:
@enable_signaling:启用 fence 的软件信号通知。
对于具备硬件到硬件(hw→hw)信号能力的 fence 实现,可以通过实现此操作来启用必要的中断、
或向命令流中插入命令等,从而在仅需要 hw→hw 同步的常见情况下避免这些高开销的操作。
此回调在首次调用 dma_fence_wait() 或 dma_fence_add_callback() 的路径中被调用,
用于通知 fence 实现:有另一个驱动正在等待该信号(即 hw→sw 的情况)。
此回调在 IRQ 禁用的状态下被调用,因此在此回调之外的代码中,只能使用会禁用 IRQ 的自旋锁。
返回 false 表示 fence 已经完成(passed),或者发生了某些错误导致无法启用信号通知。返回 true 表示成功启用。
可以在 enable_signaling 中设置 dma_fence.error,但仅限于返回 false 时。
由于许多实现可以在 enable_signaling 被调用之前就调用 dma_fence_signal(),
因此存在一个竞争窗口:dma_fence_signal() 可能导致 fence 的最后一个引用被释放,
从而其内存被释放。为避免这种情况,此回调的实现应使用 dma_fence_get() 获取自己的引用,
并在 fence 被 signal 时释放该引用(例如通过中断处理程序)。
此回调是可选的。如果未提供此回调,则驱动必须始终保持信号通知处于启用状态。
单看这个详尽的注释,真得有可能越看越糊涂,本节试图讲清楚这个设计和用法。
1. 设计思想:Lazy Enablement(延迟启用)
Linux 内核的 dma-fence 框架采用了一个精妙的优化哲学:不用就不开,用时才开。
在 GPU 驱动中,大量的 fence 仅用于 硬件到硬件(hw→hw)同步——GPU A 的输出作为 GPU B 的输入,整个过程不需要 CPU 参与。如果每个 fence 都默认开启中断通知 CPU,会带来不必要的性能开销:
- 中断上下文切换成本
- ISR 执行开销
- 缓存污染
- 功耗增加
因此,fence 框架将"启用软件信号通知"这一行为延迟到真正有软件消费者时才执行。这就是 enable_signaling 回调的设计初衷。
核心原则:
只有当有人真正需要软件通知时(
dma_fence_wait()、dma_fence_add_callback()),才启用信号机制。
2. 回调的作用
bool (*enable_signaling)(struct dma_fence *fence);
enable_signaling 是 struct dma_fence_ops 中的一个可选回调,它的职责是:
告知 fence 的实现者:“有软件在等待这个 fence,请确保 fence 完成时能调用 dma_fence_signal()。”
具体含义取决于实现者:
- 对于硬件 fence:开启中断,使 GPU 完成时能触发 ISR →
dma_fence_signal() - 对于软件 fence:可以作为事件触发点,启动某些异步工作流
- 如果不实现(设为 NULL):框架认为信号机制始终启用
返回值语义
| 返回值 | 含义 |
|---|---|
true |
成功启用信号机制,fence 尚未完成 |
false |
fence 已经 signaled,或发生错误无法启用 |
3. 工作流程
3.1 触发路径
enable_signaling 不会被直接调用,而是由以下 fence 框架 API 间接触发:
dma_fence_add_callback() ───────┐
dma_fence_wait() ──────┼──→ enable_signaling()
dma_fence_enable_sw_signaling() ─┘
3.2 调用时序(以 dma_fence_add_callback 为例)
int dma_fence_add_callback(struct dma_fence *fence, ...)
{
spin_lock_irqsave(fence->lock, flags);
// Step 1: 检查是否已经启用过
if (!test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &fence->flags)) {
// Step 2: 设置标志位
set_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &fence->flags);
// Step 3: 调用驱动实现
if (!fence->ops->enable_signaling(fence)) {
// 返回 false: fence 已完成或错误
return -ENOENT;
}
}
// Step 4: 再次检查 signaled(防止竞争)
if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags))
return -ENOENT;
// Step 5: 挂载 callback
list_add_tail(&cb->node, &fence->cb_list);
spin_unlock_irqrestore(fence->lock, flags);
}
3.3 竞争条件与防护
存在一个关键竞争窗口:
时间线:
T1: set_bit(ENABLE_SIGNAL_BIT)
T2: [此时另一个 CPU 上 dma_fence_signal() 被调用]
T3: enable_signaling() 执行
如果 dma_fence_signal() 在 T2 发生,它会检查 ENABLE_SIGNAL_BIT:
- 已设置 → 遍历
cb_list通知所有 callback - 未设置 → 无需通知(没有软件消费者)
框架通过 先设置 ENABLE_SIGNAL_BIT,再检查 SIGNALED_BIT 来关闭这个竞争窗口,确保不会漏掉信号。
3.4 调用环境约束
┌─────────────────────────────────────────────────┐
│ enable_signaling() 的调用环境 │
├─────────────────────────────────────────────────┤
│ • IRQ 已禁用(spin_lock_irqsave) │
│ • 持有 fence->lock │
│ • 只能使用 spin_lock 类型的锁 │
│ • 不能睡眠 │
│ • 不能调用可能睡眠的内存分配 │
└─────────────────────────────────────────────────┘
4. 常规使用场景:硬件 Fence
这是 enable_signaling 最典型的用途——按需开启 GPU 完成中断。
4.1 场景描述
GPU Ring Buffer:
[cmd1] [cmd2] [cmd3] ... [cmdN]
^
fence seqno = N
正常情况(hw→hw):GPU B 通过 semaphore 等待 GPU A,不需要中断
特殊情况(hw→sw):CPU 要等 fence N 完成,需要中断通知
4.2 典型实现模式
static bool gpu_fence_enable_signaling(struct dma_fence *f)
{
struct gpu_fence *fence = to_gpu_fence(f);
struct gpu_ring *ring = fence->ring;
// 1. 快速检查:fence 是否已完成
if (gpu_fence_completed(ring, fence->seqno))
return false;
// 2. 获取 fence 引用(防止中断到来前 fence 被释放)
dma_fence_get(f);
// 3. 启用该 ring 的完成中断
gpu_irq_enable(ring->irq_src);
// 4. 再次检查(防止在开中断过程中 fence 已完成)
if (gpu_fence_completed(ring, fence->seqno)) {
gpu_irq_disable(ring->irq_src);
dma_fence_put(f);
return false;
}
return true;
}
// 对应的中断处理函数
static irqreturn_t gpu_fence_isr(int irq, void *data)
{
struct gpu_ring *ring = data;
// 遍历该 ring 上所有 pending fence,signal 已完成的
while (fence = next_pending_fence(ring)) {
if (gpu_fence_completed(ring, fence->seqno)) {
dma_fence_signal(&fence->base);
dma_fence_put(&fence->base); // 释放 enable_signaling 中获取的引用
}
}
return IRQ_HANDLED;
}
4.3 为什么要 dma_fence_get()?
enable_signaling 的文档明确要求:
实现应 grab 自己的引用 (
dma_fence_get()),在 fence 被 signal 时释放。
原因:dma_fence_signal() 可能在 enable_signaling 返回之前被调用,导致最后一个引用被释放、fence 被释放。如果 ISR 此时访问 fence → use-after-free。
竞争场景(不加引用):
CPU0 CPU1
enable_signaling(fence)
gpu_irq_enable()
gpu_fence_isr()
dma_fence_signal(fence)
// 最后一个引用释放
// fence 被 kfree
fence->seqno ← UAF!
5. 特殊使用场景:事件触发器
这里给出一个特殊的使用场景,AMDGPU KFD中的eviction fence用法。
5.1 KFD Eviction Fence
AMD KFD 驱动对 enable_signaling 有一个创造性的用法——把它当作"有人想驱逐我的 BO"的事件触发器。
背景
KFD(Kernel Fusion Driver)为每个进程的所有 BO 共享一个 eviction fence或者每个SVM BO设置一个eviction fence。这个 fence 的含义是:
- 进程共享的fence表示这个进程的用户队列还在运行,BO 不能被搬走。
- SVM BO的fence在非signal时,表示不能被搬走,为signal时表示可以搬走。
当 TTM 需要腾出 VRAM 空间时,它会尝试等待 eviction fence → 触发 enable_signaling。
实现
static bool amdkfd_fence_enable_signaling(struct dma_fence *f)
{
struct amdgpu_amdkfd_fence *fence = to_amdgpu_amdkfd_fence(f);
if (dma_fence_is_signaled(f))
return true;
if (!fence->svm_bo) {
// 调度 evict & restore 工作项
kgd2kfd_schedule_evict_and_restore_process(fence->mm, ...);
} else {
// SVM BO eviction
svm_range_schedule_evict_svm_bo(fence);
}
return false; // 关键:返回 false
}
流程图
TTM 需要 VRAM 空间
│
▼
ttm_bo_evict()
│
▼
amdgpu_bo_move() → 设置 GPU scheduler job
│
▼
GPU scheduler 发现 eviction fence 未 signaled
│
▼
dma_fence_add_callback(eviction_fence)
│
▼
enable_signaling()
│
├─→ schedule_evict_and_restore_process()
│ │
│ ▼ (异步 work item)
│ 暂停用户队列(quiesce)
│ │
│ ▼
│ dma_fence_signal(eviction_fence) ← 软件主动 signal
│ │
│ ▼
│ TTM 收到通知,搬移 BO
│ │
│ ▼
│ 恢复用户队列(restore)
│
└─→ return false(告诉框架:fence 还没好,等 signal)
设计巧妙之处
| 方面 | 说明 |
|---|---|
| 不需要硬件中断 | 纯软件驱动的 signal |
enable_signaling = 事件触发器 |
"有人想驱逐我"这个事件的通知点 |
| 返回 false | 不是表示失败,而是"我已经知道了,会异步 signal" |
| 延迟执行 | 只有真正需要驱逐时才暂停队列,平时不影响性能 |
5.2 与常规用法的对比
| 常规(硬件 fence) | KFD eviction fence | |
|---|---|---|
| 谁 signal | 硬件中断 → ISR | 驱动 work item |
enable_signaling 做什么 |
开中断 | 启动 eviction 流程 |
| 返回值 | true(已启用) | false(会异步处理) |
| 语义 | “请通知我 fence 完成” | “有人要驱逐我的内存” |
6. 不实现 enable_signaling 的情况
如果驱动的 fence ops 中 enable_signaling = NULL:
// dma-fence.c 框架代码中的处理
if (!fence->ops->enable_signaling ||
fence->ops->enable_signaling(fence)) {
// NULL 被视为"始终启用"
}
适用场景:
- 中断始终开启的简单驱动
- 纯软件 fence(如
drm_sched_fence),驱动自己知道何时 signal - 不需要优化 hw→hw 路径的情况
7. 常见陷阱与最佳实践
7.1 陷阱
- 忘记
dma_fence_get():导致 use-after-free - 在
enable_signaling中睡眠:它在spin_lock_irqsave中被调用 - 忽略竞争:enable 期间 fence 可能已被 signal
- 返回 true 但不确保能 signal:callback 永远不会触发,造成死锁
7.2 最佳实践
static bool my_fence_enable_signaling(struct dma_fence *f)
{
// 1. 先检查是否已完成
if (hw_fence_completed(f))
return false;
// 2. 拿引用
dma_fence_get(f);
// 3. 启用通知机制(中断/轮询/其他)
enable_notification(f);
// 4. 再次检查(关闭竞争窗口)
if (hw_fence_completed(f)) {
disable_notification(f);
dma_fence_put(f);
return false;
}
return true;
}
8. 总结
enable_signaling 体现了内核设计中"按需付费"的哲学:
┌─────────────────────────────────────────────────────────────┐
│ enable_signaling 全景 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 设计思想: Lazy Enablement - 不用就不开,用时才开 │
│ │
│ 触发条件: 有软件消费者需要知道 fence 何时完成 │
│ (wait / callback / enable_sw_signaling) │
│ │
│ 常规用途: 开启硬件中断,让 ISR 能调用 dma_fence_signal │
│ │
│ 特殊用途: 作为事件触发器,启动异步工作流 │
│ (如 KFD eviction fence) │
│ │
│ 不实现: 框架视为信号始终启用 │
│ │
│ 核心约束: IRQ disabled / 不能睡眠 / 必须防竞争 │
│ │
└─────────────────────────────────────────────────────────────┘
理解了 enable_signaling,就理解了 dma-fence 框架中软件通知路径的入口点——它是连接"硬件完成"与"软件感知"的桥梁,也是驱动对"何时付出通知成本"做出决策的控制点。但很多驱动里都不实现这个回调,默认就是可通知的。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)