6.3.2 剖析的 ww_mutex 提供了多锁死锁避免的原语,但其「取号 → 遍历加锁 → 遇 -EDEADLK 回退 → 从冲突锁重试」的样板逻辑,若由每个驱动手写,既繁琐又极易在错误路径上遗漏回退而泄漏锁。drm_exec 正是对这一模式的官方封装:调用方只需声明"要锁哪些 GEM 对象、各预留几个 fence slot",回退与重试全部交由框架处理。第八章命令提交的 amdgpu_cs_parser_bos、以及页表更新等所有批量锁 BO 的路径,都建立在它之上。
1. 它解决什么
drm_exec 的职责,用其源码 DOC 的原话即:“abstracts the retry loop necessary for locking multiple GEM objects”——抽象出锁定多个 GEM 对象所必需的重试循环。其行为约定为:
- 一旦在加锁某对象时检测到竞争(ww_mutex 返回 -EDEADLK),清理流程会解锁此前已锁定的全部对象,并在重来时优先锁定那个引发冲突的对象;
- 对象锁定后,可选择在其 dma_resv 上预留 fence slot,供后续无分配地挂载 fence。
2. 数据结构
struct drm_exec {
u32 flags; /* DRM_EXEC_INTERRUPTIBLE_WAIT / IGNORE_DUPLICATES */
struct ww_acquire_ctx ticket; /* 6.3.2 的 acquire context(事务票据)*/
unsigned int num_objects; /* 已锁定对象数 */
unsigned int max_objects; /* objects 数组容量 */
struct drm_gem_object **objects; /* 已锁定对象数组 */
struct drm_gem_object *contended; /* 上次因竞争而回退的对象 */
struct drm_gem_object *prelocked; /* 回退后已抢先锁住的对象 */
};
其中 ticket 即 6.3.2 的 ww_acquire_ctx——drm_exec 本质上就是围绕一个 ww_acquire_ctx 管理一组 GEM 对象锁的容器。contended 与 prelocked 是回退机制的关键:前者记住"上次锁不上的那个对象",后者标记"重来时已把它抢先锁住"。
3. 使用范式
标准用法由一组宏构成(源码 DOC 给出的模板):
struct drm_exec exec;
struct drm_gem_object *obj;
unsigned long index;
drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0);
drm_exec_until_all_locked(&exec) {
ret = drm_exec_prepare_obj(&exec, boA, 1);
drm_exec_retry_on_contention(&exec);
if (ret)
goto error;
ret = drm_exec_prepare_obj(&exec, boB, 1);
drm_exec_retry_on_contention(&exec);
if (ret)
goto error;
}
drm_exec_for_each_locked_object(&exec, index, obj) {
dma_resv_add_fence(obj->resv, fence, DMA_RESV_USAGE_READ);
}
drm_exec_fini(&exec);
三个宏各司其职:
| drm_exec_until_all_locked(exec) | 外层重试循环:循环体每次从"零对象锁定"的干净状态开始,直至一次性锁定全部对象且无竞争 |
| drm_exec_prepare_obj(exec, obj, n) | 锁定 obj 并在其 dma_resv 上预留 n 个 fence slot |
| drm_exec_retry_on_contention(exec) | 若刚才的加锁遇到竞争,则跳回循环头重来 |
3.4 一个形象的比喻:凭号牌占储物柜
在深入代码前,不妨把整个机制想象成一群人在健身房同时预定一排储物柜(每个储物柜 = 一个 BO):
- 进门取号(ww_acquire_init / drm_exec_init):每人入场领一个号牌,号越小代表来得越早(越"老")。
- 按清单逐个占柜(prepare_obj):你拿着自己要用的储物柜清单,一个个去上锁。
- 遇到冲突(-EDEADLK):若某个柜子已被一个号牌更小(更老)的人占着,规矩要求你立刻退让——把自己已占的柜子全部让出,回到门口重来(unlock_all + 回到循环头)。
- 号牌不变(复用同一 stamp):重来时你不重新领号,仍用最初那张——否则你会越退越"年轻",永远抢不过别人而饿死。
- 优先直奔冲突柜(contended / prelocked):重来时你不再从头乱抢,而是先走到刚才让你吃亏的那个柜子前排队死等,拿到它之后再去占其余的。
- slot预留(fence slot):占到柜子后,你还顺手在柜门上预留好几个挂钩,供稍后挂东西(fence)用,免得到时手忙脚乱地临时找钩子(临时分配内存)。
由于"最老的人"永远不必退让,他总能一次性占齐所有柜子并办完事,随后次老者补上……整个队伍必然向前推进,既不会死锁,也不会有人永远排不上。后面的源码,本质上就是把这套"取号—占柜—让位—直奔冲突柜"的规矩用 C 宏与指针精确实现出来。
阅读提示:到这里,drm_exec 的用法已经讲完——多数情况下,你只需照上面的模板写「init → until_all_locked { prepare_obj + retry_on_contention } → 遍历 → fini」即可正确使用它,无须关心内部如何回退。后续第 4、5 节将深入其实现原理(计算跳转、状态机、contended/prelocked 配合),涉及较多内核技巧;若你只想会用而非精通,可以放心跳过这两节,直接阅读第 6 节的 fence slot 预留。
4. until_all_locked 的实现:计算跳转 + cleanup
drm_exec_until_all_locked 的宏展开颇具技巧——它用 GCC 的**标签地址(计算跳转)**实现"跳回循环体开头",因为 C 不允许在循环体内定义可被外部 goto 的标签:
#define drm_exec_until_all_locked(exec) \\
__PASTE(__drm_exec_, __LINE__): \\
for (void *__drm_exec_retry_ptr; ({ \\
__drm_exec_retry_ptr = &&__PASTE(__drm_exec_, __LINE__);\\
(void)__drm_exec_retry_ptr; \\
drm_exec_cleanup(exec); \\
});)
#define drm_exec_retry_on_contention(exec) \\
do { \\
if (unlikely(drm_exec_is_contended(exec))) \\
goto *__drm_exec_retry_ptr; \\
} while (0)
循环条件调用的 drm_exec_cleanup 是状态机的核心,它决定"是否继续循环":
bool drm_exec_cleanup(struct drm_exec *exec)
{
if (likely(!exec->contended)) { /* 无竞争:完成 */
ww_acquire_done(&exec->ticket);
return false; /* 退出循环,保持已锁对象 */
}
if (likely(exec->contended == DRM_EXEC_DUMMY)) { /* 首次进入 */
exec->contended = NULL;
ww_acquire_init(&exec->ticket, &reservation_ww_class);
return true; /* 进入循环体 */
}
drm_exec_unlock_all(exec); /* 有竞争:解锁全部,重来 */
exec->num_objects = 0;
return true;
}
其三条分支恰好对应循环的三种时刻:
#mermaid-svg-tCbsdFayhBwzgvaB{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tCbsdFayhBwzgvaB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tCbsdFayhBwzgvaB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tCbsdFayhBwzgvaB .error-icon{fill:#552222;}#mermaid-svg-tCbsdFayhBwzgvaB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tCbsdFayhBwzgvaB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tCbsdFayhBwzgvaB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tCbsdFayhBwzgvaB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tCbsdFayhBwzgvaB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tCbsdFayhBwzgvaB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tCbsdFayhBwzgvaB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tCbsdFayhBwzgvaB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tCbsdFayhBwzgvaB .marker.cross{stroke:#333333;}#mermaid-svg-tCbsdFayhBwzgvaB svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tCbsdFayhBwzgvaB p{margin:0;}#mermaid-svg-tCbsdFayhBwzgvaB .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-tCbsdFayhBwzgvaB .cluster-label text{fill:#333;}#mermaid-svg-tCbsdFayhBwzgvaB .cluster-label span{color:#333;}#mermaid-svg-tCbsdFayhBwzgvaB .cluster-label span p{background-color:transparent;}#mermaid-svg-tCbsdFayhBwzgvaB .label text,#mermaid-svg-tCbsdFayhBwzgvaB span{fill:#333;color:#333;}#mermaid-svg-tCbsdFayhBwzgvaB .node rect,#mermaid-svg-tCbsdFayhBwzgvaB .node circle,#mermaid-svg-tCbsdFayhBwzgvaB .node ellipse,#mermaid-svg-tCbsdFayhBwzgvaB .node polygon,#mermaid-svg-tCbsdFayhBwzgvaB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tCbsdFayhBwzgvaB .rough-node .label text,#mermaid-svg-tCbsdFayhBwzgvaB .node .label text,#mermaid-svg-tCbsdFayhBwzgvaB .image-shape .label,#mermaid-svg-tCbsdFayhBwzgvaB .icon-shape .label{text-anchor:middle;}#mermaid-svg-tCbsdFayhBwzgvaB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tCbsdFayhBwzgvaB .rough-node .label,#mermaid-svg-tCbsdFayhBwzgvaB .node .label,#mermaid-svg-tCbsdFayhBwzgvaB .image-shape .label,#mermaid-svg-tCbsdFayhBwzgvaB .icon-shape .label{text-align:center;}#mermaid-svg-tCbsdFayhBwzgvaB .node.clickable{cursor:pointer;}#mermaid-svg-tCbsdFayhBwzgvaB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tCbsdFayhBwzgvaB .arrowheadPath{fill:#333333;}#mermaid-svg-tCbsdFayhBwzgvaB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tCbsdFayhBwzgvaB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tCbsdFayhBwzgvaB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tCbsdFayhBwzgvaB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tCbsdFayhBwzgvaB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tCbsdFayhBwzgvaB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tCbsdFayhBwzgvaB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tCbsdFayhBwzgvaB .cluster text{fill:#333;}#mermaid-svg-tCbsdFayhBwzgvaB .cluster span{color:#333;}#mermaid-svg-tCbsdFayhBwzgvaB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tCbsdFayhBwzgvaB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tCbsdFayhBwzgvaB rect.text{fill:none;stroke-width:0;}#mermaid-svg-tCbsdFayhBwzgvaB .icon-shape,#mermaid-svg-tCbsdFayhBwzgvaB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tCbsdFayhBwzgvaB .icon-shape p,#mermaid-svg-tCbsdFayhBwzgvaB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tCbsdFayhBwzgvaB .icon-shape .label rect,#mermaid-svg-tCbsdFayhBwzgvaB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tCbsdFayhBwzgvaB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tCbsdFayhBwzgvaB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tCbsdFayhBwzgvaB :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
== DUMMY(首次)
== NULL(无竞争)
指向某对象(发生过竞争)
是
否
进入 until_all_locked→ drm_exec_cleanup
contended?
ww_acquire_init 取号返回 true,执行循环体
ww_acquire_done返回 false,退出循环
unlock_all + num_objects=0返回 true,重来
循环体:逐个 prepare_obj
retry_on_contention检测到竞争?
goto 计算跳转回到循环头
全部锁定 → 下轮 cleanup 走 NULL 分支退出
初始时 exec->contended 被置为哨兵值 DRM_EXEC_DUMMY,确保第一次进入循环时走 ww_acquire_init 取号分支。
5. 加锁与回退:contended / prelocked 的配合
真正的加锁在 drm_exec_lock_obj 中完成,其精妙之处在于回退后如何优先锁住冲突对象:
int drm_exec_lock_obj(struct drm_exec *exec, struct drm_gem_object *obj)
{
ret = drm_exec_lock_contended(exec); /* ① 先处理上次的冲突对象 */
if (unlikely(ret))
return ret;
if (exec->prelocked == obj) { /* ② 冲突对象已被抢先锁住 */
drm_gem_object_put(exec->prelocked);
exec->prelocked = NULL;
return 0;
}
ret = dma_resv_lock(obj->resv, &exec->ticket); /* ③ 正常加锁 */
if (unlikely(ret == –EDEADLK)) { /* ④ 竞争:记住它,回退 */
drm_gem_object_get(obj);
exec->contended = obj;
return –EDEADLK;
}
...
return drm_exec_obj_locked(exec, obj); /* ⑤ 记入 objects 数组 */
}
配合 drm_exec_lock_contended——它在重来循环的开头被调用,用 ww_mutex 的 _slow 变体(对应 6.3.2 的 dma_resv_lock_slow)率先锁住上次冲突的对象:
static int drm_exec_lock_contended(struct drm_exec *exec)
{
struct drm_gem_object *obj = exec->contended;
if (likely(!obj))
return 0;
exec->contended = NULL;
dma_resv_lock_slow(obj->resv, &exec->ticket); /* 阻塞式先拿到它 */
drm_exec_obj_locked(exec, obj);
exec->prelocked = obj; /* 标记:已抢先 */
return 0;
}
这样便实现了 DOC 承诺的"回退时优先锁定冲突对象":一旦某对象引发 -EDEADLK,框架解锁全部、把它记为 contended;下一轮循环开头先用慢路径把它锁牢(prelocked),从而保证事务在有限轮次内收敛——这正是 6.3.2 Wait-Die "较年轻者回退、以原 stamp 重试"的工程化落地。
ww_mutex/dma_resv
lock_obj
循环体
ww_mutex/dma_resv
lock_obj
循环体
#mermaid-svg-AE5boNhTvfU05fjR{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AE5boNhTvfU05fjR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AE5boNhTvfU05fjR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AE5boNhTvfU05fjR .error-icon{fill:#552222;}#mermaid-svg-AE5boNhTvfU05fjR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AE5boNhTvfU05fjR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AE5boNhTvfU05fjR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AE5boNhTvfU05fjR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AE5boNhTvfU05fjR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AE5boNhTvfU05fjR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AE5boNhTvfU05fjR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AE5boNhTvfU05fjR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AE5boNhTvfU05fjR .marker.cross{stroke:#333333;}#mermaid-svg-AE5boNhTvfU05fjR svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AE5boNhTvfU05fjR p{margin:0;}#mermaid-svg-AE5boNhTvfU05fjR .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AE5boNhTvfU05fjR text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-AE5boNhTvfU05fjR .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AE5boNhTvfU05fjR .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-AE5boNhTvfU05fjR .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-AE5boNhTvfU05fjR .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-AE5boNhTvfU05fjR #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-AE5boNhTvfU05fjR .sequenceNumber{fill:white;}#mermaid-svg-AE5boNhTvfU05fjR #sequencenumber{fill:#333;}#mermaid-svg-AE5boNhTvfU05fjR #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-AE5boNhTvfU05fjR .messageText{fill:#333;stroke:none;}#mermaid-svg-AE5boNhTvfU05fjR .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AE5boNhTvfU05fjR .labelText,#mermaid-svg-AE5boNhTvfU05fjR .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-AE5boNhTvfU05fjR .loopText,#mermaid-svg-AE5boNhTvfU05fjR .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-AE5boNhTvfU05fjR .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AE5boNhTvfU05fjR .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-AE5boNhTvfU05fjR .noteText,#mermaid-svg-AE5boNhTvfU05fjR .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-AE5boNhTvfU05fjR .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AE5boNhTvfU05fjR .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AE5boNhTvfU05fjR .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AE5boNhTvfU05fjR .actorPopupMenu{position:absolute;}#mermaid-svg-AE5boNhTvfU05fjR .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-AE5boNhTvfU05fjR .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AE5boNhTvfU05fjR .actor-man circle,#mermaid-svg-AE5boNhTvfU05fjR line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-AE5boNhTvfU05fjR :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
cleanup 解锁全部 + num_objects=0
boX 成为 prelocked,优先锁定
prepare_obj(boX)
1
dma_resv_lock(ticket)
2
-EDEADLK(竞争)
3
contended = boX,返回
4
retry_on_contention → goto 循环头
5
新一轮首个 prepare_obj
6
lock_contended:dma_resv_lock_slow(boX)
7
6. prepare_obj:加锁 + 预留 fence slot
drm_exec_prepare_obj 在锁定之上叠加了 fence slot 预留:
int drm_exec_prepare_obj(struct drm_exec *exec, struct drm_gem_object *obj,
unsigned int num_fences)
{
ret = drm_exec_lock_obj(exec, obj);
if (ret)
return ret;
ret = dma_resv_reserve_fences(obj->resv, num_fences); /* 预留 slot */
if (ret) {
drm_exec_unlock_obj(exec, obj);
return ret;
}
return 0;
}
预留 fence slot 的意义:后续向 dma_resv 挂载 fence(dma_resv_add_fence)时若临时扩容会触发内存分配;而挂载往往发生在禁止分配的临界区(如第八章 submit 持 notifier_lock 时)。因此必须在加锁阶段就把所需 slot 数一次性备足。调用方按"本次会向该 BO 挂几个 fence"传入 num_fences——例如命令提交按 gang 中 job 数量预留。
7. 收尾与遍历
- drm_exec_fini 调用 drm_exec_unlock_all,以加锁的逆序逐个 dma_resv_unlock 并释放 GEM 引用,再 ww_acquire_fini 归还票据;
- drm_exec_for_each_locked_object 遍历 objects[] 数组,是加锁成功后统一挂载 fence、执行 validate 等操作的标准迭代器(第八章 parser_bos 即以它遍历所有锁定对象逐一 ttm_bo_validate)。
8. 小结
- drm_exec 把 ww_mutex(6.3.2)的"取号—加锁—-EDEADLK 回退—重试"样板封装为 until_all_locked + prepare_obj + retry_on_contention 三个宏;
- until_all_locked 借助标签地址计算跳转实现回循环,由 drm_exec_cleanup 状态机在"首次取号 / 无竞争退出 / 有竞争重来"三分支间切换;
- contended + prelocked 配合 dma_resv_lock_slow,实现"回退后优先锁定冲突对象",保证事务收敛——即 Wait-Die 的工程化落地;
- prepare_obj 在加锁之上预留 fence slot,为后续禁分配临界区中的 dma_resv_add_fence 铺路;
- 它是第八章命令提交与页表更新等一切批量锁 BO 路径的统一入口,将驱动从手写回退逻辑中彻底解放。


