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

📅 2026/6/18 7:16:49
第七章:GPU Scheduler 分析:7.6 调度循环与流控 — sched_main 核心流程
1. 引言一切在此串联前面五篇分别介绍了设计目标7.1、scheduler 实例7.2、entity7.3、job7.4和双 fence7.5。本篇将它们串联成一个运转中的系统——分析sched_main.c中的调度循环如何选取 entity、弹出 job、检查 credit、调用run_job()、以及完成后的free_job()流程。核心问题从用户态 push 一个 job 到它被硬件执行完毕scheduler 内部经历了哪些步骤2. 总体架构两个 Work ItemGPU 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之间完全并行触发时机事件触发的 workdrm_sched_entity_push_job()work_run_job通过drm_sched_wakeup()drm_sched_job_done()work_free_job完成后需要释放work_free_job执行完毕work_run_jobcredit 归还后可能可以跑新 job3. 调度循环主函数drm_sched_run_job_work()drm_sched_run_job_work()——整个调度器的心脏staticvoiddrm_sched_run_job_work(structwork_struct*w){structdrm_gpu_scheduler*schedcontainer_of(w,...);structdrm_sched_entity*entity;structdrm_sched_job*sched_job;structdma_fence*fence;// ① 选取 entityentitydrm_sched_select_entity(sched);if(!entity)return;// 无就绪 entity退出// ② 弹出 job含依赖检查sched_jobdrm_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);// ④ 调用驱动回调发射到硬件fencesched-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);}// ⑥ 唤醒等待者 重新排队自己处理下一个 jobwake_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 选取 Entitydrm_sched_select_entity()staticstructdrm_sched_entity*drm_sched_select_entity(structdrm_gpu_scheduler*sched){for(iDRM_SCHED_PRIORITY_KERNEL;isched-num_rqs;i){entity(policyFIFO)?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;}关键规则严格优先级从 KERNEL → HIGH → NORMAL → LOW 遍历高优先级 rq 有就绪 entity 就选中不看低优先级两种策略由模块参数drm_sched_policy控制FIFO 策略staticstructdrm_sched_entity*drm_sched_rq_select_entity_fifo(structdrm_gpu_scheduler*sched,structdrm_sched_rq*rq){for(rbrb_first_cached(rq-rb_tree_root);rb;rbrb_next(rb)){entityrb_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 提交量大就饿死其他 entityRound Robin 策略staticstructdrm_sched_entity*drm_sched_rq_select_entity_rr(structdrm_gpu_scheduler*sched,structdrm_sched_rq*rq){entityrq-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(entityrq-current_entity)break;}returnNULL;found:rq-current_entityentity;// 记录位置下次从这之后继续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 弹出 Jobdrm_sched_entity_pop_job()选中 entity 后从它的 job_queue 取出队首 jobstructdrm_sched_job*drm_sched_entity_pop_job(structdrm_sched_entity*entity){sched_jobdrm_sched_entity_queue_peek(entity);// 检查所有依赖while((entity-dependencydrm_sched_job_dependency(sched_job,entity))){if(drm_sched_entity_add_dependency_cb(entity,sched_job))returnNULL;// 依赖未就绪注册回调后返回}// guilty 检查if(entity-guiltyatomic_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-entityNULL;// 断开 job→entity 指针returnsched_job;}如果依赖未就绪drm_sched_entity_add_dependency_cb()会注册 fence 回调回调触发时重新唤醒 scheduler通过drm_sched_wakeup()。此时pop_job()返回 NULLrun_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_jobdrm_sched_entity_queue_peek(entity);// 安全阀超过 credit_limit 的 job 被截断到 limitif(s_job-creditssched-credit_limit)s_job-creditssched-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_add在drm_sched_job_begin()之前确保并发安全。归还发生在drm_sched_job_done()中此时 work_free_job 被触发它在最后调用drm_sched_run_job_queue()重新激活 run_job_work——形成流控反馈环路。4.4 发射到硬件run_job()fencesched-ops-run_job(sched_job);这是驱动的 backend 回调。语义将 job 的命令写入硬件 ring buffer返回一个dma_fence*硬件 fence当硬件执行完命令时信号返回 NULLjob 同步完成无需等待返回 ERR_PTRjob 失败紧随其后drm_sched_fence_scheduled(s_fence,fence);这会设置 parent fence 并信号 scheduled fence详见 X.5。4.5 完成回调注册if(!IS_ERR_OR_NULL(fence)){rdma_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直接调用 donefence 为 NULL/ERR立即 done4.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*schedcontainer_of(w,...);bool have_more;jobdrm_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);joblist_first_entry_or_null(sched-pending_list,...);if(jobdma_fence_is_signaled(job-s_fence-finished)){list_del_init(job-list);cancel_delayed_work(sched-work_tdr);// 更新下一个 job 的 timestamp更准确的 TDR 起点nextlist_first_entry_or_null(...);if(next){next-s_fence-scheduled.timestampdma_fence_timestamp(job-s_fence-finished);*have_moredma_fence_is_signaled(next-s_fence-finished);drm_sched_start_timeout(sched);// 为下一个 job 重启 TDR}}else{jobNULL;}spin_unlock(sched-job_list_lock);returnjob;}关键点只取 pending_list 的队首jobFIFO 顺序释放取出后取消 TDR 定时器为下一个 job 重新启动更新下一个 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 归还后尝试新 job7. 流控机制深度分析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不会自我重入——调度器进入等待状态。当硬件完成某个 jobdrm_sched_job_done()归还 credit 并触发work_free_job。work_free_job最后调用drm_sched_run_job_queue()重新激活work_run_job——此时 credit 已释放新 job 可以执行。7.3 安全阀超大 Jobif(s_job-creditssched-credit_limit){dev_WARN(sched-dev,Jobs may not exceed the credit limit, truncate.\n);s_job-creditssched-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 恢复流程drm_sched_stop()→drm_sched_wqueue_stop()→ 等待 work 完成驱动执行 GPU resetdrm_sched_start()→ 重新提交 pending jobs →drm_sched_wqueue_start()8.3 TDR 超时控制unsignedlongdrm_sched_suspend_timeout(structdrm_gpu_scheduler*sched){// 将 timeout 延长到 MAX_SCHEDULE_TIMEOUT等效暂停 TDRmod_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_wq11. 小结维度要点执行模型基于 ordered workqueue 的两个 work item事件驱动选取策略严格优先级 同优先级内 FIFO红黑树或 Round Robin流控credit_count/credit_limit满时不调度释放后自动恢复自驱动run_job_work 末尾自我入队 free_job_work 末尾激活 run_job停止条件无就绪 entity、credit 满、或 pause_submittrue暂停/恢复pause_submit轻量或 wqueue_stop/start完全含 syncpending_list已发射未完成的 job 队列TDR 监控对象串行保证ordered workqueue 保证同 scheduler 的 run/free 不并发下一篇将深入 TDR 机制——当 pending_list 队首 job 超时未完成时drm_sched_job_timedout()如何触发驱动的 GPU 恢复流程。