一个计数更新正确,不代表相关对象仍然存活;快照可以重试,也不代表错误访问能撤回。同步需要同时说明互斥、顺序与寿命。
| 查阅目标 | 阅读位置 |
| 锁与上下文 | 第 1~4 节 |
| 一致快照与原子操作 | 第 5~6 节 |
| 发布、等待与退出 | 第 7~8 节 |
同步的目标不只是防止两个 CPU 同时写同一地址,还包括维持多字段不变量、限制对象寿命、保证数据发布顺序,以及避免同一 CPU 上的中断重入。选锁前应先列出所有读写者及其上下文。
例如队列由 head、tail 和 count 构成,把 count 改成 atomic 并不能保护节点链接;读者还可能在 count 更新前看到新节点,或在节点释放后继续访问。首先写清“什么时候队列对读者可见、谁能删除、谁负责释放”。
1. 按约束选择,而不是按所谓重量排序
| 需求 | 常见选择 | 不能忽略的条件 |
| 可睡眠任务之间保护复合状态 | mutex | 同一拥有者释放,不支持递归 |
| 真正原子/中断上下文中的短临界区 | 合适 spinlock/raw spinlock | 核实 RT 语义和中断重入 |
| 多读者需要保持稳定共享状态 | rw_semaphore | 持有期间允许阻塞也可能拖延写者 |
| 少量标量快照可重读 | seqcount/seqlock | 写者串行、读者可重试、无指针寿命漏洞 |
| 读路径低开销地访问可替换对象 | RCU | 另外处理写者互斥与延迟回收 |
| 独立原子状态或计数 | atomic/refcount 等 | 原子性、顺序和对象寿命分别设计 |
| 等待一次操作结束或条件改变 | completion / wait queue | 它们不取代保护共享数据的锁 |
读多写少只是统计特征,不能自动决定使用 RCU 或 seqlock。读者是否必须看到最新值、是否可睡眠、是否允许重试、是否需要跨临界区持有对象,都会改变选择。
2. Spinlock 与本地屏蔽分别做什么
在非 PREEMPT_RT 内核中,spin_lock() 自身会禁用抢占来保持相应临界区语义,不要求调用者事先关闭抢占。但它不自动屏蔽本地 hardirq。
假设任务持有锁 L,随后本 CPU 的中断 handler 也获取 L:handler 自旋等待任务释放,任务又无法在 handler 返回前运行,形成自锁死循环。需要在合适访问侧使用 irqsave 等协议,避免这种重入。
spin_lock_irqsave() 保存本地中断状态、屏蔽中断并取锁,对应 irqrestore 恢复原状态。
_irq() / unlock_irq 不保存原状态,调用者必须满足其入口状态约定。
_bh() 处理本地 bottom-half/softirq 并发,不能据此认为 hardirq 或 NMI 也被屏蔽。
关闭本地中断不能防止其他 CPU 访问共享数据;真正的跨 CPU 互斥来自锁。NMI 不受普通中断屏蔽控制,在 NMI 也会使用的数据需单独设计。
PREEMPT_RT 改变了 spinlock_t 的实现与语义
RT 内核的普通 spinlock_t 基于可阻塞的实时锁机制,不应假设获取后禁用抢占;其 hardirq 后缀也不等同于非 RT 上的本地硬中断屏蔽。raw_spinlock_t 保留严格自旋和相应原子语义,用于真正需要它的底层场景。
这不是鼓励把所有锁替换成 raw spinlock:过长不可抢占区会损害实时性,而且持有 raw 锁时不能再调用可能阻塞的普通 RT spinlock 获取路径。类型与嵌套规则见锁类型文档。
3. qspinlock 与 mutex 的竞争路径
qspinlock 将紧凑锁状态与排队等待结合,包含无竞争、pending 和队列路径;竞争者可通过 MCS 风格节点减少所有 CPU 持续争用同一个锁字的开销。它不是“所有竞争者永远只碰自己的 cache line”,解锁、队头和尾部交接仍需共享状态。
实现的位布局还受配置影响,应读取 include/asm-generic/qspinlock_types.h,不能把 locked/pending/tail 随意按字节画成固定 ABI。架构和虚拟化也可能采用不同实现。
mutex 的无竞争路径快速取得所有权,竞争时可在合适条件下乐观自旋或睡眠等待。唤醒等待者并不保证它立即在 CPU 上运行;公平性、handoff 与 owner 状态处理也不能缩写成一次 owner=0。
持 mutex 可以在允许上下文中睡眠,但要考虑睡眠路径是否反过来需要同一锁;mutex_lock_interruptible() 等接口可返回未取得锁的结果,调用方必须检查。普通 mutex 不等于提供优先级继承的 rt_mutex。参见mutex 设计。
4. rwsem 与 percpu rwsem 是不同接口
rw_semaphore 允许多个读者或一个写者;percpu_rw_semaphore 是专门接口,利用 per-CPU 读侧状态和相应同步机制优化某些读多写少场景。不是普通 rwsem 到某个版本后自动全部变成 per-CPU。
读锁仍可能等待写锁;一个读者长时间睡眠会阻挡写者。RT 下多个读者与优先级继承的关系尤其需要注意,不能承诺任意配置中都有严格的等待时延上限。
如果从读锁直接尝试升级成写锁,可能与其他升级者相互等待。应按接口支持情况释放后重查条件、改为一开始取写锁,或设计专门转换协议。
5. Seqlock:得到一致快照,代价是可能重试
写者进入时把序列推进到写入状态,结束时再次推进;读者复制数据后检查序列是否变化。write_seqlock() / unlock 已管理序列,调用者不能在两者之间再次手工 seq++。
下面是可独立编译的内核 C 翻译单元示例,不包含模块注册。它展示两个标量的不变量 high = low + 1;读取结果只在一次成功检查后使用。
#include <linux/compiler.h>
#include <linux/seqlock.h>
#include <linux/types.h>
static DEFINE_SEQLOCK(range_lock);
static u32 range_low;
static u32 range_high = 1;
void kb_set_range(u32 low);
void kb_get_range(u32 *low, u32 *high);
void kb_set_range(u32 low)
{
write_seqlock(&range_lock);
WRITE_ONCE(range_low, low);
WRITE_ONCE(range_high, low + 1);
write_sequnlock(&range_lock);
}
void kb_get_range(u32 *low, u32 *high)
{
unsigned int seq;
u32 a, b;
do {
seq = read_seqbegin(&range_lock);
a = READ_ONCE(range_low);
b = READ_ONCE(range_high);
} while (read_seqretry(&range_lock, seq));
*low = a;
*high = b;
}
| 读者的动作 | 暂存结果 | 是否可交给调用者 |
| 先读取旧 low | a = 0 | 不可,快照尚未验证 |
| 写者更新为 (10, 11),读者再读 high | (a, b) = (0, 11) | 不可,组合破坏 high = low + 1 |
| 序列检查发现更新 | 丢弃临时组合并重读 | 只有检查成功才可使用 |
重试区间内不能基于临时值执行不可撤回的外部副作用。
本例假设访问上下文与普通 seqlock 匹配;若中断读者能打断写者,写者还需相应本地屏蔽,否则读者可能等待被自己打断的写者恢复。纯 seqcount 本身也不串行多个写者,必须另有写侧锁,并满足不可抢占等规则。
在写者更新途中分别读取两个字段,可以主动拼出一份混合版本。序列验证决定这份临时结果能否使用,不能把已经读到两个值当成一致快照。
正在呈现知识画面
拼出来的两个值,能当作同一份快照吗?临时读数可以不一致;只有序列未变且为偶数时才使用结果。重试保护一致快照,不撤销非法指针访问,也不允许不可重复副作用。
{"id":"seqlock-interleavings","title":"拼出来的两个值,能当作同一份快照吗?","summary":"临时读数可以不一致;只有序列未变且为偶数时才使用结果。重试保护一致快照,不撤销非法指针访问,也不允许不可重复副作用。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"two\"><div class=\"card\"><header data-i18n=\"writer\"></header><div class=\"actions\"><button id=\"beginWrite\" data-i18n=\"beginWrite\"></button></div><div class=\"actions\"><button id=\"writeLow\" data-i18n=\"writeLow\"></button></div><div class=\"actions\"><button id=\"writeHigh\" data-i18n=\"writeHigh\"></button></div><div class=\"actions\"><button id=\"endWrite\" data-i18n=\"endWrite\"></button></div></div><div class=\"card\"><header data-i18n=\"reader\"></header><div class=\"actions\"><button id=\"beginRead\" data-i18n=\"beginRead\"></button></div><div class=\"actions\"><button id=\"readLow\" data-i18n=\"readLow\"></button></div><div class=\"actions\"><button id=\"readHigh\" data-i18n=\"readHigh\"></button></div><div class=\"actions\"><button id=\"validate\" data-i18n=\"validate\"></button></div></div></div><div class=\"track-row\"><span data-i18n=\"shared\"></span><div id=\"shared\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"draft\"></span><div id=\"draft\" class=\"track\"></div></div><div class=\"card\"><header data-i18n=\"result\"></header><div id=\"result\" class=\"readout\"></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"note\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n.card .actions{margin:8px 0}.card .actions button{min-height:5em}.readout{min-height:5.3em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}let seq=0,low=0,high=1,writing=false,target=0,wrote=[false,false],reading=false,start=null,a=null,b=null,result='initial';function draw(){cells('shared',[{text:'low = '+low},{text:'high = '+high},{text:'seq = '+seq,cls:seq%2?'gold':'on'}]);cells('draft',[{text:'a = '+(a??'—')},{text:'b = '+(b??'—')},{text:'seq₀ = '+(start??'—'),cls:reading?'on':''}]);$('#beginWrite').disabled=writing;$('#writeLow').disabled=!writing||wrote[0];$('#writeHigh').disabled=!writing||wrote[1];$('#endWrite').disabled=!writing||!wrote.every(Boolean);$('#beginRead').disabled=reading||seq%2===1;$('#readLow').disabled=!reading;$('#readHigh').disabled=!reading;$('#validate').disabled=!reading||a===null||b===null;set('result',t(result)+(result==='initial'?'':'\\n('+a+', '+b+')'));set('explanation',t('note'));}$('#beginWrite').onclick=()=>{writing=true;seq++;target=low+10;wrote=[false,false];draw();};$('#writeLow').onclick=()=>{low=target;wrote[0]=true;draw();};$('#writeHigh').onclick=()=>{high=target+1;wrote[1]=true;draw();};$('#endWrite').onclick=()=>{writing=false;seq++;draw();};$('#beginRead').onclick=()=>{reading=true;start=seq;a=b=null;result='initial';draw();};$('#readLow').onclick=()=>{a=low;draw();};$('#readHigh').onclick=()=>{b=high;draw();};$('#validate').onclick=()=>{result=start===seq&&seq%2===0?'accepted':'retry';reading=false;draw();};$('#reset').onclick=()=>{seq=low=0;high=1;writing=reading=false;wrote=[false,false];start=a=b=null;result='initial';draw();};draw();","audio":false,"strings":{"accepted":"接受:快照可以使用","beginRead":"开始 / 重新读取","beginWrite":"开始更新","draft":"临时值与开始序列","endWrite":"结束更新","heading":"拼出来的两个值,能当作同一份快照吗?","initial":"尚未验证","intro":"在写者和读者两列自由交错操作。故意让 low 来自旧版、high 来自新版,再检查序列是否允许提交这份结果。","note":"尝试:开始读取 → 读 low → 完成一次写更新 → 读 high → 验证。暂存区会保留混合值,但结果不会被接受。","readHigh":"读取 high","readLow":"读取 low","reader":"读者:临时副本","reset":"重置","result":"最近一次验证","retry":"拒绝:丢弃并重试","scope":"一个已串行化的写者,两个标量 high=low+1;序列无限精度,不考虑回绕。读侧开始按钮在奇数期间禁用,示意 read_seqbegin 等待稳定序列;每个按钮把内部访问拆开供观察,不是可复制的内核实现。","scopeLabel":"模型范围与约定","shared":"共享值与当前序列","validate":"验证这份快照","writeHigh":"更新 high","writeLow":"更新 low","writer":"写者:共享字段"}}
快照一致与指针寿命分开保证
重试不能修复已经发生的非法访问。 如果读者通过快照中的指针访问已被释放对象,再发现序列变了,仍然是 UAF。指针寿命必须由 RCU、引用或其他机制保护。读侧不能在可能重试的区域做不可重复的外部副作用。见 seqcount/seqlock。
6. 原子操作、refcount 与顺序
atomic_read/set、无返回值 RMW、带返回值 RMW 及 acquire/release/relaxed 变体的顺序保证不同,不能把所有 atomic 操作称为全屏障。失败的条件操作也需单独检查返回与顺序语义。
对象引用应优先使用合适的 refcount_t 等接口,利用溢出/下溢防护与特定寿命语义;一般计数器不自动防止“从零复活”或安全获取已经可能释放的对象。
CAS 能原子比较替换一个状态,却不自动消除 ABA:某个值经历 A→B→A 后,比较仍可成功。无锁结构往往还需要版本、回收协议和严格的顺序设计。见 atomic 类型。
7. 发布数据需要匹配的观察关系
一个只发布一次、发布后不再修改 payload 的例子:写者先填写 payload,再 smp_store_release(&ready, 1);读者通过 smp_load_acquire(&ready) 观察到该发布值后,才读取 payload。
release/acquire 的作用来自匹配的发布与观察,不是任意两个 CPU 各执行一次屏障就自动同步。若 ready 重用、payload 循环覆盖,还需要防止旧读者与新写者竞争的额外协议。
| 工具 | 所约束的层次 |
| READ_ONCE / WRITE_ONCE | 单次访问相关的编译器行为 |
| barrier() | 编译顺序 |
| smp_* 屏障 | 相应跨 CPU 内存排序 |
| 设备访问器与 dma_* 屏障 | MMIO/DMA 的相应设备访问协议 |
这些操作不是统一的“刷新缓存”,单次访问约束也不独立建立完整发布关系。参见内存屏障文档。
下面固定读者已经读到 ready=1,只改变发布与获取的配对。观察哪些结果仍允许出现,从结果集合理解顺序约束,而不是把源代码上下位置直接当成跨 CPU 先后。
正在呈现知识画面
看到 ready=1,为什么还需要匹配的发布关系?只有读者通过 acquire 观察到对应 release 发布值,才能沿匹配关系推导此前 payload 写入对后续读取的约束。READ_ONCE/WRITE_ONCE 本身不建立这条完整关系。
{"id":"publication-ordering","title":"看到 ready=1,为什么还需要匹配的发布关系?","summary":"只有读者通过 acquire 观察到对应 release 发布值,才能沿匹配关系推导此前 payload 写入对后续读取的约束。READ_ONCE/WRITE_ONCE 本身不建立这条完整关系。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"check\"><input type=\"checkbox\" id=\"release\"><span data-i18n=\"release\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"acquire\"><span data-i18n=\"acquire\"></span></label><div class=\"two\"><div class=\"card\"><header data-i18n=\"writer\"></header><div class=\"event\">payload = 1</div><div id=\"write-edge\" class=\"order-edge\">↓</div><div id=\"store\" class=\"event\"></div></div><div class=\"card\"><header data-i18n=\"reader\"></header><div id=\"load\" class=\"event\"></div><div id=\"read-edge\" class=\"order-edge\">↓</div><div class=\"event\">read payload</div></div></div><div class=\"rf\" data-i18n=\"observes\"></div><p class=\"caption\" data-i18n=\"choose\"></p><div class=\"outcomes\"><div id=\"candidate-bad\"><code data-i18n=\"bad\"></code><strong id=\"verdict-bad\"></strong></div><div id=\"candidate-good\"><code data-i18n=\"good\"></code><strong id=\"verdict-good\"></strong></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"unmatched\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"matched\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n.event{border:1px solid var(--rule);border-radius:5px;min-height:5em;padding:9px 5px;display:flex;align-items:center;font:12px var(--viz-font-ui);overflow-wrap:anywhere}.order-edge{text-align:center;font-size:28px;color:var(--rule)}.order-edge.on{color:var(--accent)}.rf{border-block:1px dashed var(--accent);padding:12px 4px;font-size:12px;text-align:center}.outcomes>div{display:flex;justify-content:space-between;gap:10px;padding:16px 0;border-bottom:1px solid var(--rule);font-size:12px;min-height:5em}.outcomes code{overflow-wrap:anywhere}.outcomes strong{font-weight:500;color:var(--accent)}.outcomes .forbidden code{text-decoration:line-through;color:var(--muted)}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}function draw(){const r=$('#release').checked,a=$('#acquire').checked,matched=r&&a;set('store',r?'store_release(ready, 1)':'WRITE_ONCE(ready, 1)');set('load',a?'load_acquire(ready) = 1':'READ_ONCE(ready) = 1');$('#write-edge').classList.toggle('on',r);$('#read-edge').classList.toggle('on',a);$('#candidate-bad').classList.toggle('forbidden',matched);set('verdict-bad',t(matched?'forbidden':'allowed'));set('verdict-good',t('allowed'));set('explanation',t(matched?'matched':'unmatched'));}$('#release').onchange=$('#acquire').onchange=draw;draw();","audio":false,"strings":{"acquire":"读侧:load_acquire","allowed":"允许","bad":"ready=1, payload=0","choose":"对照两个候选结果","forbidden":"禁止","good":"ready=1, payload=1","heading":"看到 ready=1,为什么还需要匹配的发布关系?","intro":"分别启用写侧 release 和读侧 acquire。观察事件之间的顺序边,以及“ready=1、payload=0”是否仍属于允许结果。","matched":"release 与观察到它的 acquire 匹配,完整链条建立,payload=0 的候选被排除。","observes":"ready 读取观察到这次写入(reads-from)","reader":"读者","release":"写侧:store_release","reset":"重置","scope":"一次性发布,初始 payload=ready=0,之后 payload 不再改写。固定读者观察 ready=1,展示通用弱 LKMM 下可能/禁止的结果,不预测某 CPU 的发生频率;省略其他同步。图中边表示顺序推理,不是把屏障解释成刷新缓存。","scopeLabel":"模型范围与约定","unmatched":"缺少匹配关系,不能排除旧 payload。只打开一端也不能补齐两端之间的顺序证明。","writer":"写者"}}
8. 等待必须与条件和寿命配合
wait queue 使用 wait_event 等接口在正确协议下登记等待并检查条件,唤醒者先更新条件再通知。单独写一个 if (!ready) schedule() 会在检查与睡眠之间丢掉通知。
completion 适合一次或受控重复的完成事件;超时返回并不取消异步操作,等待对象及回调上下文仍必须保持有效。销毁工作时先阻止新提交,再同步取消/等待,最后释放资源,且等待期间不能持有完成路径需要的锁。
如何验证这些约束,见同步调试。