カウンタの更新が正しくても、関連オブジェクトがまだ生存していることを意味しません。スナップショットで再試行できたとしても、不正なアクセスを取り消せるわけではありません。同期では、排他制御、順序、寿命のすべてを同時に説明する必要があります。
参照目標 読書位置
ロックとコンテキスト 第 1~4 節
一貫性のあるスナップショットとアトミック操作 第 5~6 節
公開、待機、終了 第 7~8 節
同期の目的は、2つのCPUが同じアドレスに同時に書き込むのを防ぐだけでなく、複数フィールドの不変条件の維持、オブジェクト寿命の制限、データの公開順序の保証、および同一CPU上での割り込み再帰の回避も含みます。ロックを選択する前に、すべての読み書き実行コンテキストを列挙すべきです。
例えば、キューは head、tail、count で構成されています。count を atomic に変更しても、ノードのリンクは保護されません。読者は count が更新される前に新しいノードを見るか、ノードが解放された後もアクセスを続ける可能性があります。まず、「いつキューが読者に見えるか、誰が削除できるか、誰が解放を責任を持って行うか」を明確に記述します。
アクセスコンテキストをまず列挙し、その後で排他制御とローカルマスクを決定する
タスクコンテキスト
スリープ可能か?
softirq / hardirq
現在のロック保持者を中断できるか?
他のCPU
同じ状態を同時に読み書きしているか?
共通の同期プロトコル
ロック順序、オブジェクト寿命、可視性、終了時のクリーンアップ
ローカル割り込みを無効化しても、他のCPUのアクセスはロックされません。単一のアトミックフィールドも、オブジェクト全体を自動的に保護するわけではありません。
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の割り込みハンドラも L を取得すると仮定します。ハンドラはタスクが解放するまでスピニング待ちをし、タスクはハンドラが戻る前に実行できないため、デッドロック(自鎖)が発生します。このような再帰を避けるために、適切なアクセス側で 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が同じロックワードを継続的に争うオーバーヘッドを軽減できます。それは「すべての競合者が常に自分のキャッシュラインだけを触る」わけではなく、解放、キューの先頭と末尾の引き継ぎには共有状態が必要です。
実装のビットレイアウトは設定にも依存するため、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 は複数の読者または1人の書き込みを許可します。percpu_rw_semaphore は専用のインターフェースであり、per-CPU の読側状態と対応する同期メカニズムを利用して、読み込みが多く書き込みが少ない特定のシナリオを最適化します。通常の rwsem があるバージョンに達すると自動的にすべてが per-CPU になるわけではありません。
読側ロックでも書き込みロックを待つことがあります。1人の読者が長時間スリープすると、書き込みをブロックします。RT 環境では、複数の読者と優先度継承の関係に特に注意が必要であり、任意の設定で厳格な待機遅延の上限があることを約束できません。
読側ロックから直接書き込みロックへのアップグレードを試みると、他のアップグレード試行者と相互に待つ可能性があります。インターフェースのサポート状況に応じて、解放後に条件を再確認するか、最初から書き込みロックを取得するか、または専用の変換プロトコルを設計してください。
5. Seqlock: 一貫性のあるスナップショットを取得するが、再試行の代償がある
書き込み側はシーケンスを進めて書き込み状態に入り、終了時に再度シーケンスを進めます。読者はデータをコピーした後、シーケンスが変化していないか確認します。write_seqlock() / unlock はシーケンスを管理しているため、呼び出し元はその間で手動で seq++ を行ってはいけません。
以下は、モジュール登録を含まない独立してコンパイル可能なカーネル C 翻訳単位の例です。2つのスカラーの不変条件 high = low + 1 を示しており、読み取り結果は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 , * 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 自体は複数の書き込み側を直列化しないため、別の書き込み側ロックが必要であり、非プリエンプティブなどのルールを満たす必要があります。
書き込み側の更新中に2つのフィールドを別々に読み取ると、意図的に混合されたバージョンを組み立てることができます。シーケンス検証は、この一時結果の使用可否を決定し、すでに2つの値を読み取ったからといって一貫性のあるスナップショットと見なしてはいけません。
図を準備しています 再読み込み
組み立てられた2つの値を、同一のスナップショットとして扱えるか? 一時読み取り値は不一致になる可能性があります。シーケンスが変化せず、かつ偶数である場合のみ結果を使用します。再試行は一貫性のあるスナップショットを保護しますが、不正なポインタアクセスを取り消すものではなく、再現実行不可能な副作用を許可するものでもありません。
{"id":"seqlock-interleavings","title":"組み立てられた2つの値を、同一のスナップショットとして扱えるか?","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":"組み立てられた2つの値を、同一のスナップショットとして扱えるか?","initial":"未検証","intro":"書き込み側と読者側の列で操作を自由にインターリーブします。low を旧バージョンから、high を新バージョンから故意に取得し、シーケンスがこの結果の提出を許可しているか確認します。","note":"試行:読み取り開始 → low 読み取り → 書き込み更新完了 → high 読み取り → 検証。一時領域には混合値が残るが、結果は承認されない。","readHigh":"high を読み取る","readLow":"low を読み取る","reader":"読者:一時コピー","reset":"リセット","result":"直近の検証","retry":"拒否:破棄して再試行","scope":"直列化された書き込み側、2つのスカラー high=low+1;シーケンスは無限精度で、オーバーフローは考慮しない。読側開始ボタンは奇数期間中は無効化され、read_seqbegin が安定したシーケンスを待つことを示唆している。各ボタンは内部アクセスを観察用に分割するものであり、コピー可能なカーネル実装ではない。","scopeLabel":"モデル範囲と規約","shared":"共有値と現在のシーケンス","validate":"このスナップショットを検証","writeHigh":"high を更新","writeLow":"low を更新","writer":"書き込み側:共有フィールド"}}
スナップショットの一貫性とポインタ寿命は別々に保証する
再試行は、すでに発生した不正なアクセスを修復できません。 読者がスナップショット内のポインタを通じて解放されたオブジェクトにアクセスし、その後シーケンスが変化したことに気づいたとしても、それは依然として UAF(Use-After-Free)です。ポインタ寿命は、RCU、参照カウント、または他のメカニズムによって保護されなければなりません。読側は、再試行が発生する可能性のある領域で再現実行不可能な外部副作用を行ってはなりません。seqcount/seqlock を参照してください。
6. アトミック操作、refcount と順序
atomic_read/set、戻り値なしの RMW、戻り値ありの RMW、および acquire/release/relaxed 変体の順序保証は異なり、すべての atomic 操作をフルバリアと見なしてはいけません。失敗する条件付き操作についても、戻り値と順序セマンティクスを別途確認する必要があります。
オブジェクト参照は、オーバーフロー/アンダーフロー保護と特定の寿命セマンティクスを利用する、適切な refcount_t などのインターフェースを優先して使用すべきです。一般的なカウンタは、「ゼロからの復活」や、すでに解放される可能性のあるオブジェクトの安全な取得を防ぐものではありません。
CAS は状態をアトミックに比較・置換できますが、ABA を自動的に解消するわけではありません。ある値が A→B→A を経た後でも、比較は成功する可能性があります。ロックフリー構造では、バージョン管理、回収プロトコル、厳格な順序設計も必要になることがよくあります。atomic 型 を参照してください。
7. データの公開には対応する観察関係が必要
1回だけ公開され、公開後にペイロードを変更しない例:書き込み側はまずペイロードを埋め、次に smp_store_release(&ready, 1) を実行します。読者は smp_load_acquire(&ready) を通じてこの公開値を観察した後、初めてペイロードを読み取ります。
release/acquire の効果は、対応する公開と観察の組み合わせによるもので、任意の2つのCPUがそれぞれ1回ずつバリアを実行しただけでは自動的に同期されません。ready が再利用され、ペイロードが循環して上書きされる場合、旧読者と新書き込み側の競合を防ぐための追加プロトコルが必要です。
ツール 制約されるレベル
READ_ONCE / WRITE_ONCE 単一アクセスに関連するコンパイラ動作
barrier() コンパイル順序
smp_* バリア 対応するCPU間メモリ順序付け
デバイスアクセサと dma_* バリア MMIO/DMA の対応するデバイスアクセスプロトコル
これらの操作は統一された「キャッシュフラッシュ」ではなく、単一アクセスの制約だけでも完全な公開関係を独立して確立するものではありません。メモリバリアドキュメント を参照してください。
以下は、読者がすでに ready=1 を読み取った状態を固定し、公開と取得のペアリングのみを変更します。どの結果が依然として許可されるかを観察し、結果の集合から順序制約を理解します。ソースコードの上下の位置関係をそのまま CPU 間の先後として扱わないでください。
図を準備しています 再読み込み
ready=1 を見たのに、なぜ対応する公開関係が必要なのか? 読者が acquire を通じて対応する release 公開値を観察した場合のみ、対応する関係を通じて、それ以前のペイロード書き込みがその後の読み取りに与える制約を推論できます。READ_ONCE/WRITE_ONCE 自体はこの完全な関係を作成しません。
{"id":"publication-ordering","title":"ready=1 を見たのに、なぜ対応する公開関係が必要なのか?","summary":"読者が acquire を通じて対応する release 公開値を観察した場合のみ、対応する関係を通じて、それ以前のペイロード書き込みがその後の読み取りに与える制約を推論できます。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":"2つの候補結果を照合","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":"1回の公開、初期 payload=ready=0、その後ペイロードは上書きされない。読者が ready=1 を観察することを固定し、一般的な弱 LKMM 下での可能/禁止結果を示す。特定の CPU での発生頻度を予測するものではなく、他の同期を省略している。図中のエッジは順序推論を表しており、バリアをキャッシュフラッシュとして解釈するものではない。","scopeLabel":"モデル範囲と規約","unmatched":"対応する関係が不足しており、旧ペイロードを除外できない。片側だけを開いても、両端間の順序証明を補完できない。","writer":"書き込み側"}}
8. 待機は条件と寿命と組み合わせて行う必要がある
wait queue は、wait_event などのインターフェースを使用して、正しいプロトコル下で待機を登録し条件をチェックします。通知側はまず条件を更新し、その後通知を行います。単に if (!ready) schedule() と書くだけでは、チェックとスリープの間に通知を見逃してしまいます。
completion は、1回または制御された繰り返しの完了イベントに適しています。タイムアウトの返却は非同期操作をキャンセルするものではなく、待機オブジェクトおよびコールバックコンテキストは依然として有効であり続ける必要があります。リソースを解放する際は、まず新しいサブミットを阻止し、次に同期的にキャンセル/待機を行い、最後にリソースを解放します。また、待機中は完了パスに必要なロックを保持してはいけません。
これらの制約をどのように検証するかについては、同期デバッグ を参照してください。