当前位置: 首页 > news >正文

第七章:GPU Scheduler 分析:7.6 调度循环与流控 — sched_main 核心流程

1. 引言:一切在此串联

前面五篇分别介绍了设计目标(7.1)、scheduler 实例(7.2)、entity(7.3)、job(7.4)和双 fence(7.5)。本篇将它们串联成一个运转中的系统——分析sched_main.c中的调度循环如何选取 entity、弹出 job、检查 credit、调用run_job()、以及完成后的free_job()流程。

核心问题:从用户态 push 一个 job 到它被硬件执行完毕,scheduler 内部经历了哪些步骤?

2. 总体架构:两个 Work Item

GPU scheduler不使用独立内核线程,而是基于 workqueue 的两个 work item 驱动:

submit_wq (ordered workqueue) ├── work_run_job → 选 entity → pop job → credit 检查 → run_job() └── work_free_job → 取 finished job → free_job() → 归还 credit

为什么用 ordered workqueue?

  • 保证同一 scheduler 的 run_job 和 free_job 不会并发执行
  • 省去大量锁竞争——选取 entity、pop job、run_job 调用天然串行
  • 但不同 scheduler(不同 ring)之间完全并行

触发时机:

事件触发的 work
drm_sched_entity_push_job()work_run_job(通过drm_sched_wakeup()
drm_sched_job_done()work_free_job(完成后需要释放)
work_free_job执行完毕work_run_job(credit 归还后可能可以跑新 job)

3. 调度循环主函数:drm_sched_run_job_work()

drm_sched_run_job_work()——整个调度器的心脏:

staticvoiddrm_sched_run_job_work(structwork_struct*w){structdrm_gpu_scheduler*sched=container_of(w,...);structdrm_sched_entity*entity;structdrm_sched_job*sched_job;structdma_fence*fence;// ① 选取 entityentity=drm_sched_select_entity(sched);if(!entity)return;// 无就绪 entity,退出// ② 弹出 job(含依赖检查)sched_job=drm_sched_entity_pop_job(entity);if(!sched_job){complete_all(&entity->entity_idle);drm_sched_run_job_queue(sched);// 重新排队自己return;}// ③ 记账 credit + 加入 pending_listatomic_add(sched_job->credits,&sched->credit_count);drm_sched_job_begin(sched_job);// ④ 调用驱动回调:发射到硬件fence=sched->ops->run_job(sched_job);complete_all(&entity->entity_idle);// ⑤ 信号 scheduled fence + 注册完成回调drm_sched_fence_scheduled(s_fence,fence);if(!IS_ERR_OR_NULL(fence)){dma_fence_add_callback(fence,&sched_job->cb,drm_sched_job_done_cb);dma_fence_put(fence);}else{drm_sched_job_done(sched_job,IS_ERR(fence)?PTR_ERR(fence):0);}// ⑥ 唤醒等待者 + 重新排队自己(处理下一个 job)wake_up(&sched->job_scheduled);drm_sched_run_job_queue(sched);}

注意最后的drm_sched_run_job_queue(sched)——这让 work_run_job自我重入(重新入队 submit_wq),形成一个"循环"。只要有就绪的 entity 和足够的 credit,它就会持续处理 job。

4. 步骤详解

4.1 选取 Entity:drm_sched_select_entity()

staticstructdrm_sched_entity*drm_sched_select_entity(structdrm_gpu_scheduler*sched){for(i=DRM_SCHED_PRIORITY_KERNEL;i<sched->num_rqs;i++){entity=(policy==FIFO)?drm_sched_rq_select_entity_fifo(sched,sched->sched_rq[i]):drm_sched_rq_select_entity_rr(sched,sched->sched_rq[i]);if(entity)break;// 找到就不再看低优先级 rq}returnIS_ERR(entity)?NULL:entity;}

关键规则:

  1. 严格优先级:从 KERNEL → HIGH → NORMAL → LOW 遍历,高优先级 rq 有就绪 entity 就选中,不看低优先级
  2. 两种策略:由模块参数drm_sched_policy控制
FIFO 策略
staticstructdrm_sched_entity*drm_sched_rq_select_entity_fifo(structdrm_gpu_scheduler*sched,structdrm_sched_rq*rq){for(rb=rb_first_cached(&rq->rb_tree_root);rb;rb=rb_next(rb)){entity=rb_entry(rb,structdrm_sched_entity,rb_tree_node);if(drm_sched_entity_is_ready(entity)){if(!drm_sched_can_queue(sched,entity))returnERR_PTR(-ENOSPC);// credit 不够reinit_completion(&entity->entity_idle);break;}}returnrb?entity:NULL;}
  • 使用rb_tree_root(红黑树),按oldest_job_waiting时间排序
  • 全局最老 job 优先:哪个 entity 最早入队的 job 等得最久,就先调度它
  • 保证公平性——不会因为某个 entity 提交量大就饿死其他 entity
Round Robin 策略
staticstructdrm_sched_entity*drm_sched_rq_select_entity_rr(structdrm_gpu_scheduler*sched,structdrm_sched_rq*rq){entity=rq->current_entity;// 从 current_entity 之后开始找list_for_each_entry_continue(entity,&rq->entities,list){if(drm_sched_entity_is_ready(entity))gotofound;}// 绕回从头找list_for_each_entry(entity,&rq->entities,list){if(drm_sched_entity_is_ready(entity))gotofound;if(entity==rq->current_entity)break;}returnNULL;found:rq->current_entity=entity;// 记录位置,下次从这之后继续returnentity;}
  • 维护current_entity指针,每次从上次位置继续
  • 每个 entity 轮流获得一次执行机会
Entity "就绪"的定义
staticinlinebooldrm_sched_entity_is_ready(structdrm_sched_entity*entity){if(!spsc_queue_count(&entity->job_queue))returnfalse;// 队列为空if(READ_ONCE(entity->dependency))returnfalse;// 有未解决的依赖returntrue;}

4.2 弹出 Job:drm_sched_entity_pop_job()

选中 entity 后,从它的 job_queue 取出队首 job:

structdrm_sched_job*drm_sched_entity_pop_job(structdrm_sched_entity*entity){sched_job=drm_sched_entity_queue_peek(entity);// 检查所有依赖while((entity->dependency=drm_sched_job_dependency(sched_job,entity))){if(drm_sched_entity_add_dependency_cb(entity,sched_job))returnNULL;// 依赖未就绪,注册回调后返回}// guilty 检查if(entity->guilty&&atomic_read(entity->guilty))dma_fence_set_error(&sched_job->s_fence->finished,-ECANCELED);// 更新 last_scheduledrcu_assign_pointer(entity->last_scheduled,&sched_job->s_fence->finished);spsc_queue_pop(&entity->job_queue);sched_job->entity=NULL;// 断开 job→entity 指针returnsched_job;}

如果依赖未就绪,drm_sched_entity_add_dependency_cb()会注册 fence 回调,回调触发时重新唤醒 scheduler(通过drm_sched_wakeup())。此时pop_job()返回 NULL,run_job_work 重新入队等待。

4.3 Credit 检查与记账

Credit 检查发生在选取 entity 阶段drm_sched_can_queue()),而非 pop_job 之后:

staticbooldrm_sched_can_queue(structdrm_gpu_scheduler*sched,structdrm_sched_entity*entity){s_job=drm_sched_entity_queue_peek(entity);// 安全阀:超过 credit_limit 的 job 被截断到 limitif(s_job->credits>sched->credit_limit)s_job->credits=sched->credit_limit;returndrm_sched_available_credits(sched)>=s_job->credits;}staticu32drm_sched_available_credits(structdrm_gpu_scheduler*sched){returnsched->credit_limit-atomic_read(&sched->credit_count);}

Credit 生命周期:

credit_count ─────────────────┬────────────────────────────────── pop_job 后 │ atomic_add(job->credits) ↑ 增加 run_job 成功 │ (已计入) hw_fence 信号 │ drm_sched_job_done() │ atomic_sub(job->credits) ↓ 归还 ─────────────────┴──────────────────────────────────

关键设计:credit 在run_job()之前就计入(atomic_adddrm_sched_job_begin()之前),确保并发安全。归还发生在drm_sched_job_done()中,此时 work_free_job 被触发,它在最后调用drm_sched_run_job_queue()重新激活 run_job_work——形成流控反馈环路

4.4 发射到硬件:run_job()

fence=sched->ops->run_job(sched_job);

这是驱动的 backend 回调。语义:

  • 将 job 的命令写入硬件 ring buffer
  • 返回一个dma_fence*(硬件 fence),当硬件执行完命令时信号
  • 返回 NULL:job 同步完成(无需等待)
  • 返回 ERR_PTR:job 失败

紧随其后:

drm_sched_fence_scheduled(s_fence,fence);

这会设置 parent fence 并信号 scheduled fence(详见 X.5)。

4.5 完成回调注册

if(!IS_ERR_OR_NULL(fence)){r=dma_fence_add_callback(fence,&sched_job->cb,drm_sched_job_done_cb);if(r==-ENOENT)drm_sched_job_done(sched_job,fence->error);// 已信号}
  • 正常路径:注册drm_sched_job_done_cb到硬件 fence
  • 硬件 fence 已经信号(-ENOENT):直接调用 done
  • fence 为 NULL/ERR:立即 done

4.6 Pending List 与 TDR 计时

drm_sched_job_begin()将 job 加入pending_list并启动超时定时器:

staticvoiddrm_sched_job_begin(structdrm_sched_job*s_job){spin_lock(&sched->job_list_lock);list_add_tail(&s_job->list,&sched->pending_list);drm_sched_start_timeout(sched);spin_unlock(&sched->job_list_lock);}

pending_list 中的 job 按提交顺序排列(FIFO)。TDR 只监视队首 job——如果它超时未完成,则认为 GPU 挂了。

5. 完成与释放:drm_sched_free_job_work()

drm_sched_free_job_work()

staticvoiddrm_sched_free_job_work(structwork_struct*w){structdrm_gpu_scheduler*sched=container_of(w,...);bool have_more;job=drm_sched_get_finished_job(sched,&have_more);if(job){sched->ops->free_job(job);// 驱动释放 job 资源if(have_more)drm_sched_run_free_queue(sched);// 还有完成的 job}drm_sched_run_job_queue(sched);// credit 归还了,尝试跑新 job}

drm_sched_get_finished_job()

staticstructdrm_sched_job*drm_sched_get_finished_job(structdrm_gpu_scheduler*sched,bool*have_more){spin_lock(&sched->job_list_lock);job=list_first_entry_or_null(&sched->pending_list,...);if(job&&dma_fence_is_signaled(&job->s_fence->finished)){list_del_init(&job->list);cancel_delayed_work(&sched->work_tdr);// 更新下一个 job 的 timestamp(更准确的 TDR 起点)next=list_first_entry_or_null(...);if(next){next->s_fence->scheduled.timestamp=dma_fence_timestamp(&job->s_fence->finished);*have_more=dma_fence_is_signaled(&next->s_fence->finished);drm_sched_start_timeout(sched);// 为下一个 job 重启 TDR}}else{job=NULL;}spin_unlock(&sched->job_list_lock);returnjob;}

关键点:

  1. 只取 pending_list 的队首job(FIFO 顺序释放)
  2. 取出后取消 TDR 定时器,为下一个 job 重新启动
  3. 更新下一个 job 的 scheduled timestamp——让 TDR 超时从前一个 job 完成时开始计算

6. 完整数据流时序

用户态: ioctl submit → drm_sched_job_init() → 分配 s_fence → drm_sched_job_arm() → 初始化 fence,选择 scheduler → dma_resv_add_fence(bo, &s_fence->finished) → drm_sched_entity_push_job() → spsc_queue_push() → job 入队 entity → drm_sched_wakeup() → queue_work(work_run_job) work_run_job (submit_wq): drm_sched_select_entity() → 按优先级遍历 rq[] → FIFO: rb_tree 最老优先 / RR: 轮转 → drm_sched_can_queue(): credit 检查 → 返回就绪 entity drm_sched_entity_pop_job() → 遍历 dependencies, 检查每个 fence → 依赖未就绪? 注册 cb, return NULL → 等待 → 所有依赖就绪? pop job, 断开 entity 指针 atomic_add(credits) → credit 记账 drm_sched_job_begin() → 加入 pending_list, 启动 TDR sched->ops->run_job() → 驱动发射到硬件 ring drm_sched_fence_scheduled() → ★ scheduled fence 信号 dma_fence_add_callback(hw_fence, done_cb) → 注册完成回调 drm_sched_run_job_queue() → 自我重入,处理下一个 job 硬件完成: hw_fence 信号 → drm_sched_job_done_cb() → atomic_sub(credits) → 归还 credit → drm_sched_fence_finished() → ★ finished fence 信号 → drm_sched_run_free_queue() → queue_work(work_free_job) work_free_job (submit_wq): drm_sched_get_finished_job() → 从 pending_list 取队首完成的 job sched->ops->free_job() → 驱动释放 job 资源 drm_sched_run_job_queue() → credit 归还后尝试新 job

7. 流控机制深度分析

7.1 为什么需要流控

硬件 ring buffer 容量有限。如果 scheduler 无限制地调用run_job(),ring buffer 溢出会导致:

  • 驱动必须在run_job()中阻塞(违反 workqueue 语义)
  • 或者丢弃 job(不可接受)

Credit 机制让 scheduler在软件层面背压:当已发射但未完成的 job 总 credit 达到上限时,停止发射新 job。

7.2 流控反馈环路

┌──────────────────────────────────────────────────────────────┐ │ │ │ run_job_work: │ │ can_queue()? ──No──→ 返回 ERR_PTR(-ENOSPC) │ │ │ select_entity 返回 NULL │ │ Yes run_job_work 退出(不自我重入) │ │ │ │ │ ▼ │ │ run_job() │ │ │ │ │ ├──→ credit_count += job.credits │ │ │ │ │ ▼ (等待硬件完成) │ │ job_done() │ │ │ │ │ ├──→ credit_count -= job.credits │ │ │ │ │ └──→ run_free_queue() ──→ free_job_work │ │ │ │ │ └──→ run_job_queue() ────┘ │ (重新激活 run_job) └──────────────────────────────────────────────────────────────┘

当 credit 不够时,drm_sched_select_entity()返回 NULL(因为drm_sched_can_queue()返回ERR_PTR(-ENOSPC),被转为 NULL)。此时 run_job_work不会自我重入——调度器进入"等待"状态。

当硬件完成某个 job,drm_sched_job_done()归还 credit 并触发work_free_jobwork_free_job最后调用drm_sched_run_job_queue()重新激活work_run_job——此时 credit 已释放,新 job 可以执行。

7.3 安全阀:超大 Job

if(s_job->credits>sched->credit_limit){dev_WARN(sched->dev,"Jobs may not exceed the credit limit, truncate.\n");s_job->credits=sched->credit_limit;}

如果某个 job 的 credit 超过 scheduler 的 credit_limit,强制截断到 limit。这保证了前向进展——否则这个 job 永远无法被调度(因为 available_credits 永远不够)。

8. 暂停与恢复

Scheduler 支持两种暂停机制:

8.1 pause_submit(轻量暂停)

staticvoiddrm_sched_run_job_queue(structdrm_gpu_scheduler*sched){if(!READ_ONCE(sched->pause_submit))queue_work(sched->submit_wq,&sched->work_run_job);}

pause_submit = true时,drm_sched_run_job_queue()drm_sched_run_free_queue()都不会入队新 work。效果:

  • 不会调度新 job
  • 不会释放已完成的 job
  • 已在硬件上执行的 job 不受影响

8.2 drm_sched_wqueue_stop/start(完全停止)

voiddrm_sched_wqueue_stop(structdrm_gpu_scheduler*sched){WRITE_ONCE(sched->pause_submit,true);cancel_work_sync(&sched->work_run_job);// 等待正在执行的 work 完成cancel_work_sync(&sched->work_free_job);}voiddrm_sched_wqueue_start(structdrm_gpu_scheduler*sched){WRITE_ONCE(sched->pause_submit,false);queue_work(sched->submit_wq,&sched->work_run_job);queue_work(sched->submit_wq,&sched->work_free_job);}

用于 TDR 恢复流程:

  1. drm_sched_stop()drm_sched_wqueue_stop()→ 等待 work 完成
  2. 驱动执行 GPU reset
  3. drm_sched_start()→ 重新提交 pending jobs →drm_sched_wqueue_start()

8.3 TDR 超时控制

unsignedlongdrm_sched_suspend_timeout(structdrm_gpu_scheduler*sched){// 将 timeout 延长到 MAX_SCHEDULE_TIMEOUT(等效暂停 TDR)mod_delayed_work(sched->timeout_wq,&sched->work_tdr,MAX_SCHEDULE_TIMEOUT);returnremaining;}voiddrm_sched_resume_timeout(structdrm_gpu_scheduler*sched,unsignedlongremaining){if(list_empty(&sched->pending_list))cancel_delayed_work(&sched->work_tdr);elsemod_delayed_work(sched->timeout_wq,&sched->work_tdr,remaining);}

用于长时间操作(如 VM page table 更新)期间暂停 TDR 监控,避免误触超时。

9. 自我驱动循环的完整触发链

Scheduler 没有一个永远运行的线程。它是事件驱动的:

触发事件触发链结果
用户 push jobpush_job()drm_sched_wakeup()queue_work(run_job)开始调度
run_job_work 完成一个 job末尾drm_sched_run_job_queue()继续调度下一个
依赖 fence 信号cb →drm_sched_wakeup()queue_work(run_job)被阻塞的 entity 解除
硬件 fence 信号job_done_cb()drm_sched_run_free_queue()释放 + credit 归还
free_job_work 完成末尾drm_sched_run_job_queue()credit 归还后继续调度

停止条件:当所有 entity 都空或都被依赖阻塞,且 credit 耗尽时,run_job_work 不再自我入队——调度器安静等待外部事件。

10. 与其他组件的交互图

用户态 ioctl │ ▼ drm_sched_entity_push_job() │ ▼ drm_sched_wakeup() ┌──────────────────────────────────┐ │ work_run_job │ │ │ │ select_entity() │ │ │ │ │ ▼ │ │ pop_job() │ │ │ │ │ ├── dep fence unsignaled ───┼──→ 注册 cb, 退出 │ │ │ (fence 信号后重新激活) │ ▼ │ │ can_queue()? │ │ │ │ │ ├── No (credit 不够) ───────┼──→ 退出 │ │ │ (free_job 归还后重新激活) │ ▼ │ │ run_job() ──→ 硬件 ring │ │ │ │ │ ▼ │ │ scheduled fence 信号 │ │ │ │ │ └──→ 自我重入 ──────────────┘ │ │ work_free_job │ │ │ │ get_finished_job() │ │ │ │ │ ▼ │ │ free_job() │ │ │ │ │ └──→ run_job_queue() ───────┘ │ └──────────────────────────────────┘ submit_wq

11. 小结

维度要点
执行模型基于 ordered workqueue 的两个 work item,事件驱动
选取策略严格优先级 + 同优先级内 FIFO(红黑树)或 Round Robin
流控credit_count/credit_limit,满时不调度,释放后自动恢复
自驱动run_job_work 末尾自我入队 + free_job_work 末尾激活 run_job
停止条件无就绪 entity、credit 满、或 pause_submit=true
暂停/恢复pause_submit(轻量)或 wqueue_stop/start(完全,含 sync)
pending_list已发射未完成的 job 队列,TDR 监控对象
串行保证ordered workqueue 保证同 scheduler 的 run/free 不并发

下一篇将深入 TDR 机制——当 pending_list 队首 job 超时未完成时,drm_sched_job_timedout()如何触发驱动的 GPU 恢复流程。

http://www.rkmt.cn/news/1546328.html

相关文章:

  • 上海GEO优化贴牌主体爱搜索GEO,重塑AI时代品牌曝光新路径 - 品牌报告
  • Gemini多模态原生架构:从胶水层到共生训练的技术范式迁移
  • 2026年南昌K金回收怎么选?5个关键考察点与专业机构推荐,看这篇就够了 - 本地品牌推荐
  • 性能调优与排错:GraphRAG 系统的瓶颈分析与优化实战
  • 数据科学面试避坑指南:9个暴露业务脱节的真实错误
  • 幕墙精致钢精品定制哪家好?天津市洪伟钢管靠谱吗 - myqiye
  • 智能学习助手:AutoUnipus如何让网课学习效率提升85%
  • Bulk Rename Utility批量增加后缀教程
  • 江苏做非标工业机器人的厂家哪家好?干货指南 - myqiye
  • 长治市黄金回收+白银回收+铂金回收+彩金回推荐收门店 本地靠谱店铺指南及地联系方式址和 - 大熊猫898989
  • 2026 安徽宿州全域彩钢瓦修缮四大正规企业深度测评|皖北风雪沙尘专属对比 + 工厂业主完整版避坑指南 - 本地便民网
  • 盘点哈尔滨汽车玻璃膜靠谱品牌,金马荣耀上榜 - mypinpai
  • OpenClaw MetaSKILLs 系统深度解析:AI Agent 正在学会「自己给自己写技能」
  • 2026艾芃装饰实力之选,价格透明无隐藏消费,客户口碑力荐品质保障 - mypinpai
  • 知识管理平台分类体系:如何解决技术团队的知识组织难题
  • 第七篇:进阶篇 —— 工程化与质量保障 第14章 自动化测试:构筑代码质量的防火墙
  • 深度解析 Kotlin 运算符重载:提升 Android 开发效率的核心技巧
  • 中山市黄金回收+白银回收+铂金回收+彩金回推荐收门店 本地靠谱店铺指南及地联系方式址和 - 大熊猫898989
  • wifi是怎么连接的---四次握手(四)
  • OpenGL帧缓冲与后处理全链路实战|全网独家复现FBO构建、多通道渲染、滤镜优化,助力游戏特效、AR画面、图像美化高效落地
  • 中卫市黄金回收+白银回收+铂金回收+彩金回推荐收门店 本地靠谱店铺指南及地联系方式址和 - 大熊猫898989
  • SH9多尺度实验检验矩阵设计:桌面凝聚态模拟、地面精密测量和高能天体观测三个尺度的立体化检验矩阵(世毫九实验室原创研究)
  • ArcGIS实战:从数据到地图,掌握克里金插值核心流程
  • Android应用安全:Play Integrity API检测器构建与设备完整性验证实战
  • 机器学习模型生产化落地:从可运行到可运维的四层设计
  • 构建可信模型评估数学:从业务损益出发的指标设计方法
  • STM32通用GPIO模拟驱动TM1629A数码管的轻量级代码包(含.c/.h文件与Demo)
  • 为什么你用 AI 做的网站总有一股 AI 味?教你用 FlowyAIPC 快速生成高质量官网
  • WSL 幽灵入口清理记录与技术解析
  • AI寻聘方案评估:人才地图自动绘制、推荐理由及无简历匹配