欢迎光临
我们一直在努力

第七章:GPU Scheduler 分析:7.7 超时检测与 GPU 恢复(TDR)

GPU 是独立于 CPU 运行的协处理器。一旦命令发射到硬件 ring,CPU 只能等待完成信号。如果 GPU 停止响应,硬件 fence 永远不会信号,调度链路永久阻塞。TDR(Timeout Detection and Recovery)是 scheduler 对此的系统性解决方案:检测超时 → 判定责任 → 恢复调度。


1. 全景

三阶段全景图如下:

┌──────────────────────────────┐
│ delayed_work: work_tdr │ ← drm_sched_init() 初始化
│ timeout: sched->timeout │
└──────────┬───────────────────┘
│ 超时到期
▼
drm_sched_job_timedout()
│
▼
sched->ops->timedout_job(job) ← 驱动回调
│
├── RESET: GPU reset + 恢复调度
├── NO_HANG: job 重新插入(误报)
└── ENODEV: 设备不可用,停止调度

阶段核心机制关键函数
检测 delayed_work 定时器,盯住 pending_list 队首 drm_sched_start_timeout()
判定 驱动回调 + karma 累计 + guilty 标记 timedout_job(), drm_sched_increase_karma()
恢复 stop → reset → resubmit → start drm_sched_stop(), drm_sched_start()

2. 检测:定时器如何盯住 GPU

2.1 启动与触发

static void drm_sched_start_timeout(struct drm_gpu_scheduler *sched)
{
if (sched->timeout != MAX_SCHEDULE_TIMEOUT &&
!list_empty(&sched->pending_list))
mod_delayed_work(sched->timeout_wq, &sched->work_tdr, sched->timeout);
}

调用时机:job 加入 pending_list 时、队首 job 完成后为下一个重启、reset 恢复后。

2.2 监控对象:永远是队首

TDR 监控 pending_list 队首 job(最老的已发射未完成 job)。队首完成则为下一个重启定时器。

2.3 公平计时

当队首 job 完成时,下一个 job 的计时起点被重置为"前一个完成的时刻":

next->s_fence->scheduled.timestamp = dma_fence_timestamp(&job->s_fence->finished);
drm_sched_start_timeout(sched);

每个 job 都有完整的执行时间窗口,不会因排在慢 job 后面而被误判。


3. 判定:超时后谁是罪犯

3.1 drm_sched_job_timedout()

static void drm_sched_job_timedout(struct work_struct *work)
{
sched = container_of(work, struct drm_gpu_scheduler, work_tdr.work);

spin_lock(&sched->job_list_lock);
job = list_first_entry_or_null(&sched->pending_list, ...);
if (job) {
list_del_init(&job->list); // ① 摘除(防 stop 重复插入)
spin_unlock(&sched->job_list_lock);

status = job->sched->ops->timedout_job(job); // ② 驱动判决

if (sched->free_guilty) { // ③ 释放边缘完成的 bad job
job->sched->ops->free_job(job);
sched->free_guilty = false;
}
if (status == DRM_GPU_SCHED_STAT_NO_HANG)
drm_sched_job_reinsert_on_false_timeout(sched, job); // ④ 误报重插
} else {
spin_unlock(&sched->job_list_lock);
}

if (status != DRM_GPU_SCHED_STAT_ENODEV)
drm_sched_start_timeout_unlocked(sched); // ⑤ 重启定时器
}

3.2 三种判决

返回值含义scheduler 后续动作
RESET GPU 挂了,驱动已 reset 重启定时器,继续调度
ENODEV 设备不可用 不重启定时器,调度器停止
NO_HANG 假警报 job 重新插回 pending_list

3.3 Karma 机制:识别惯犯

每次 timeout → job.karma++
karma > hang_limit → entity.guilty = true → 后续 job 全部 -ECANCELED

void drm_sched_increase_karma(struct drm_sched_job *bad)
{
if (bad->s_priority != DRM_SCHED_PRIORITY_KERNEL) { // KERNEL 优先级豁免
atomic_inc(&bad->karma);
// 通过 fence_context 匹配找到对应 entity,标记 guilty
}
}

Guilty 效果:在 drm_sched_entity_pop_job() 中,guilty entity 的所有后续 job 的 finished fence 被设为 -ECANCELED。job 仍发射到硬件,但用户态通过错误码感知惩罚。

KERNEL 豁免:VM 页表更新、SDMA 迁移等系统必要 job 不计 karma,因为 hang 可能间接损坏它们。


4. 恢复:从废墟中重建调度

4.1 两种恢复模式

模式适用架构恢复策略
Firmware Scheduler 每 ring 一个 scheduler/entity 杀死故障 entity + scheduler,不影响其他进程
Hardware Scheduler 一个 ring 服务多个 entity stop all → 隔离 guilty → GPU reset → resubmit → start all

Hardware 模式的关键:不能简单杀死 scheduler,无辜进程的 entity 还在上面,需精确隔离。

4.2 drm_sched_stop():冻结现场

void drm_sched_stop(struct drm_gpu_scheduler *sched, struct drm_sched_job *bad)
{
drm_sched_wqueue_stop(sched); // ① 停 workqueue

if (bad && bad->sched == sched)
list_add(&bad->list, &sched->pending_list); // ② bad job 插回队首

// ③ 反向遍历 pending_list
list_for_each_entry_safe_reverse(s_job, tmp, &sched->pending_list, list) {
if (s_job->s_fence->parent &&
dma_fence_remove_callback(s_job->s_fence->parent, &s_job->cb)) {
// 未完成:归还 credit,清空 parent
dma_fence_put(s_job->s_fence->parent);
s_job->s_fence->parent = NULL;
atomic_sub(s_job->credits, &sched->credit_count);
} else {
// 已完成:释放(bad job 除外)
if (bad != s_job) sched->ops->free_job(s_job);
else sched->free_guilty = true;
}
}

cancel_delayed_work(&sched->work_tdr); // ④ 取消定时器
}

为什么反向遍历? 先处理最新 job,最后处理 bad job,确保恢复时 bad job 在队首供驱动决策。

4.3 drm_sched_start():重启引擎

void drm_sched_start(struct drm_gpu_scheduler *sched, int errno)
{
list_for_each_entry_safe(s_job, tmp, &sched->pending_list, list) {
atomic_add(s_job->credits, &sched->credit_count);

if (!s_job->s_fence->parent)
drm_sched_job_done(s_job, errno ?: –ECANCELED); // 未 resubmit
else if (dma_fence_add_callback(fence, &s_job->cb, ...))
drm_sched_job_done(s_job, fence->error ?: errno); // 已完成
}

drm_sched_start_timeout_unlocked(sched);
drm_sched_wqueue_start(sched);
}

4.4 完整恢复时序

超时发生 → drm_sched_job_timedout()
├── 摘除 job_A
└── timedout_job(job_A)
├── drm_sched_increase_karma(job_A)
├── drm_sched_stop(sched, job_A)
│ ├── 停 workqueue,job_A 插回队首
│ ├── 遍历:未完成的清 parent,已完成的释放
│ └── 取消 TDR
├── GPU Reset (驱动特定)
├── drm_sched_resubmit_jobs(sched)
│ └── 对每个 pending job: run_job() → 新 hw_fence
└── drm_sched_start(sched, 0)
└── 重注册 callbacks,恢复 workqueue


5. 特殊场景

5.1 多 Ring 全局 Reset 同步

很多 GPU 的 reset 是全局的。解决方案:所有 scheduler 共享一个 ordered workqueue 作为 timeout_wq,保证 timeout handler 串行执行,避免并发 reset 竞争。

timeout_wq = alloc_ordered_workqueue("gpu_sched_timeout", 0);

Ring B 超时时,若已被 Ring A 的 reset 解决,驱动检查后返回 NO_HANG 即可。

5.2 False Timeout

驱动返回 NO_HANG 时,job 重新插回 pending_list 队首,触发 free_queue 检查(如已完成则立即释放),并重启 TDR 定时器重新计时。

5.3 暂停/恢复与主动触发

// 暂停:长时间调试/compute 场景
remaining = drm_sched_suspend_timeout(sched);
// … 操作 …
drm_sched_resume_timeout(sched, remaining);

// 主动触发:硬件中断报告故障时立即触发 TDR
drm_sched_fault(sched); // mod_delayed_work(…, 0)


6. 小结

维度要点
检测 delayed_work 定时器监控 pending_list 队首
判定 timedout_job() 三种返回值;karma 累计标记 guilty
恢复 stop(冻结 + 清理)→ reset → resubmit → start(恢复)
多 ring 共享 ordered timeout_wq 串行化
边缘情况 false timeout 重插、free_guilty 延迟释放、suspend/resume
赞(0)
未经允许不得转载:171主机测评 » 第七章:GPU Scheduler 分析:7.7 超时检测与 GPU 恢复(TDR)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址