以下は、perfbook の付録Dの kanda.motohiro@gmail.com による訳です。perfbook の訳の他の部分は、親文書を参照。
付録D リードコピーアップデートの実装
この付録は、完全装備で本番品質の RCU 実装をいくつか紹介します。これらの例を理解するには、2章から9章の題材を完全に理解している必要がありますし、Linux カーネルをかなり理解している必要があります。後者については、教科書やウェブサイトがいくつかあります。
あなたが、RCU 実装が初めてなら、9.5.3節の単純な「おもちゃの」RCU 実装から始めるのが良いでしょう。
D.1節は、「スリープ可能RCU」、SRCU を紹介します。それは、SRCUリーダーが任意にスリープできます。これは、本番品質のRCU実装としては単純な実装なので、それら実装を学び始める最初としてはよいものです。
D.2節は、クラシックRCUの高度にスケーラブルな実装の概要を紹介します。それは、数千の CPU を使うSMPシステムのために設計されたものです。D.3節は、この同じ実装の(2008年終わり時点の)コードウォークスルーに読者をお連れします。
最後に、D.4節は、実時間システムで使われるプリエンプト可能RCU実装の詳細を提供します。
D.1 スリープ可能RCU実装
クラシックRCUは、リード側クリティカルセクションが、純粋なスピンロックのクリティカルセクションと同じ規則に従うことを要求します。あらゆるブロッキングやスリーピングは厳格に禁止されます。今までこれは、しばしば、RCU を使うことの障害となっており、Paul は「スリープ可能RCU」(SRCU)への多くの要求を受け取ってきました。それは、RCUリード側クリティカルセクション内で任意のスリープ(あるいはブロック)を許すものです。Paul はこれまでは、そのような要求を全て、実現不可能であるとして拒絶してきました。なぜならば、RCUリード側クリティカルセクションでの任意のスリープは、グレースピリオドを無期限に引き伸ばすことがあります。それはつまり、グレースピリオドが終わるのを待つメモリの量が任意に大きくなる結果を生み、最終的には図D.1に楽しく描かれたような災害になるでしょう。最もありそうなのは、メモリ枯渇によるハングです。結局、システムハングにつながる可能性のある同時実行制御プリミティブは、正しく使われたとしても、存在する価値はありません。
しかし、リアルタイムカーネルで、スピンロックのクリティカルセクションがプリエンプティブであることを要求するものは、RCUリード側クリティカルセクションもプリエンプティブであることを要求します。プリエンプティブなクリティカルセクションはそうすると、ロック確保プリミティブがデッドロックを避けるためにブロックすることを要求します。するとそれは、RCUとスピンロックのクリティカルセクションが、ロックを待ってブロックできることを意味します。しかし、この2つのスリープの形式は、優先度ブーストと優先度継承が、眠っているタスクをすみやかに起こすために使われることがあるという特別な性質を持ちます。
とはいえ、リアルタイムカーネルでのRCUの使用は、「RCUリード側クリティカルセクションは決してスリープできない」と刻まれた石版の最初のひび割れでした。ただし、TCPコネクションが来るのを待ってブロックするような無期限のスリープは、リアルタイムカーネルであっても厳密に禁止されます。
クイッククイズD.1
なぜ、クラシックRCUのリード側クリティカルセクションではスリープは禁止されるのですか?
クイッククイズD.2
コンテキストスイッチを静止状態から除けば、クラシックRCUのリード側クリティカルセクションでスリープを許せるのでないですか?静止状態としては、残りの、ユーザモード実行とアイドルループを使います。
D.1.1 SRCU 実装戦略
SRCUを設計する時の最大の問題は、なんらかのタスクがRCUリード側クリティカルセクションでスリープして、無制限の数のRCUコールバックをブロックするのを防ぐことです。このゴールを達成するために、SRCUは2つの戦略を取ります。
1 クラシックRCUの call_rcu() API のように、非同期のグレースピリオドインタフェースを提供するのを拒否します。
2 グレースピリオドの検出を、SRCU を使っているそれぞれのサブシステム内に隔離します。
これらの戦略の理由を、以下の節で議論します。
D.1.1.1 非同期のグレースピリオド API を廃止する
call_rcu() API の問題は、一つのスレッドが、グレースピリオドを待つ任意の多数のメモリブロックを生成できることです。以下に示すとおりです。
1 while (p = kmalloc(sizeof(*p), GFP_ATOMIC))
2 call_rcu(&p->rcu, f);
それに対して、synchronize_rcu() を使う類似のコードは、スレッドごとに最大でも一つのグレースピリオドを待つメモリブロックを持てるだけです。
1 while (p = kmalloc(sizeof(*p),
2 GFP_ATOMIC)) {
3 synchronize_rcu();
4 kfree(&p->rcu, f);
5 }
なので、SRCUは、synchronize_rcu() 相当を提供しますが、call_rcu() は提供しません。
D.1.1.2 グレースピリオド検出を隔離する
クラシックRCUでは、一つのリード側クリティカルセクションが、全ての RCU コールバックを無期限に遅延させることが可能でした。例えば、以下のとおりです。
1 /* BUGGY: Do not use!! */
2 rcu_read_lock();
3 schedule_timeout_interruptible(longdelay);
4 rcu_read_unlock();
このたぐいの振る舞いは、RCUが、グレースピリオドの長期間の遅延に耐えられるように注意深く設計された単一のサブシステム内に限って使われるならば許されるかもしれません。一つの孤立したサブシステムの一つのRCUリード側バグが、全てのRCUユーザを遅延させることができるという事実のために、こういう長期間のRCUリード側遅延は禁止されたのです。
これを回避する一つの方法は、グレースピリオド検出を、それぞれのサブシステムベースで実行することです。そうすれば、寝ている RCUリーダは、そのリーダのサブシステム内でだけ、グレースピリオドを遅延させます。それぞれのサブシステムはグレースピリオドを待つ有限個のメモリブロックを持つことができるだけですし、サブシステムの数も有限でしょうから、グレースピリオドを待つメモリの数も全体で有限となります。あるサブシステムの設計者は(1)SRCUリード側のスリープが有限であることを保証し、(2)synchronize_srcu() を待つメモリの総量を制限する、責任があります。
脚注。例えば、SRCU で守られたハッシュテーブルは、ハッシュチェーンごとにロックを持つでしょう。そして、synchronize_srcu() を待つブロックは、ハッシュチェーン当たり、最大一つに限られます。
これがまさに、SRCU が取ったアプローチであり、次の節で説明します。
D.1.2 SRCU API と使い方
図D.2に、SRCU API を示します。以下の節は、その使い方を説明します。
D.1.2.1 初期化と後始末
SRCUを使うそれぞれのサブシステムは、struct srcu_struct を作成しなくてはいけません。この型の変数を定義しても、kmalloc() などでダイナミックにメモリを確保してもよいです。この構造体ができたら、init_srcu_struct で初期化しないといけません。成功はゼロ、失敗(例えば、メモリがないとき)はエラーコードを返します。
struct srcu_struct をダイナミックに確保した時は、それを解放する前に cleanup_srcu_struct を呼びます。同様に、struct srcu_struct が、Linuxカーネルモジュール内で定義された変数であるなら、そのモジュールをアンロードする前に、cleanup_srcu_struct を呼びます。いずれの場合も、コール元は、cleanup_srcu_struct を呼ぶ前に、全てのSRCUリード側クリティカルセクションが完了して(さらに、開始するものはない)いることを保証するように注意します。これを達成する方法の一つを、D.1.2.4に示します。
D.1.2.2 リード側プリミティブ
リード側の、srcu_read_lock() と srcu_read_unlock() プリミティブはこのように使います。
1 idx = srcu_read_lock(&ss);
2 /* read-side critical section. */
3 srcu_read_unlock(&ss, idx);
ss 変数は、 srcu_struct 構造体で、D.1.2.1 に従って初期化されています。idx 変数は、実は、srcu_read_unlock に、対応する srcu_read_lock が開始した時のグレースピリオドを伝えるための整数です。
こうして、インデックスを運ぶのは、RCU API からの決別です。それは、必要ならば、等価な情報をタスク構造体に格納していました。しかし、あるタスクは、潜在的には、任意の多数のネストしたSRCUリード側クリティカルセクションを占めることがありえるので、SRCUはこのインデックスを合理的にタスク構造体に格納することはできません。
D.1.2.3 更新側プリミティブ
synchronize_srcu()プリミティブはこのように使います。
1 list_del_rcu(p);
2 synchronize_srcu(&ss);
3 kfree(p);
クラシックRCUとの類似から推測されるように、このプリミティブは、synchronize_srcu() が始まる前に開始した、全てのSRCUリード側クリティカルセクションが完了するまでブロックします。これを表D.1に示します。ここで、CPU 1はCPU 0 のSRCUリード側クリティカルセクションが完了するのを待つだけでよいです。CPU 2のSRCU リード側クリティカルセクションの完了を待つ必要はありません。なぜならば、CPU2はCPU1がsynchronize_srcu() の実行を始めた後でないと、このクリティカルセクションを開始していないからです。最後に、CPU1の synchronize_srcu() はCPU 3のSRCU リード側クリティカルセクションの完了を待つ必要はありません。なぜならば、CPU3は、struct srcu_struct として、 s1 でなく、 s2 を使っているからです。なので、CPU3のSRCUリード側クリティカルセクションは、CPU0と2とは異なるグレースピリオドのセットに関連づいています。
srcu_batches_completed() プリミティブは、ある struct srcu_struct のグレースピリオドの進行を監視するために使うことができます。このプリミティブは、SRCU の操作を検証する、“torture tests”で使われます。
D.1.2.4 安全に後始末する
SRCU を安全に後始末するのは挑戦であることもありますが、幸運にも多くの用途ではその必要はありません。例えば、ブート時に初期化されるオペレーティングシステムカーネルでの使用は、後始末の必要がありません。しかし、ローダブルモジュールでの使用は、そのモジュールが安全にアンロードされるためには後始末が必要です。
RCU torture モジュールのようなある場合には、少数の既知のスレッドのセットだけが、特定の struct srcu_struct に対して、SRCU リード側クリティカルセクションを使っています。こういう場合には、モジュール終了コードは単に、そのスレッドのセットを停止すれば良く、それが終わるのを待って、そして後始末をします。
それ以外の場合、例えば、デバイスドライバでは、システムの任意のスレッドがSRCU リード側プリミティブを使っているかもしれません。前のパラグラフの方法を適用することもできますが、これは完全なリブートと等しくなってしまいます。それはあまり魅力的ではありません。図D.3は、リブートなしに後始末をする一つの方法です。
readside() 関数は、RCU と SRCU のリード側クリティカルセクションをオーバラップします。前者は5から11行目に、後者は10から13行目にあります。RCU リード側クリティカルセクションは、nomoresrcu 変数を守るために、Pure RCU を使います。もしこの変数が設定されていたら、後始末の途中なので、SRCU リード側クリティカルセクションに入ることはできません。その代わりに、-EINVAL を返します。そうでなく、後始末をまだ始めてないなら、SRCU リード側クリティカルセクションに進みます。
cleanup() 関数は、最初に19行目で nomoresrcu 変数を設定し、しかしそこで、全ての現在実行中の RCU リード側クリティカルセクションが完了するのを、20行目の synchronize_rcu() プリミティブで待たないといけません。cleanup() 関数が」21行目に来た時は、nomorersrcu がゼロであるのを見た可能性のある全ての readside()呼び出しは、既に11行目に達しているはずで、このため、それらのSRCU リード側クリティカルセクションに入っていなkればいけません。以降の全ての readside() 呼び出しは、8行目で抜けるので、リード側クリティカルセクションに入ることはありません。
なので、cleanup() が21行目で自分の synchronize_srcu() 呼び出しを完了したならば、全ての SRCU リード側クリティカルセクションは完了しているはずで、新しいものは開始することができません。なので、22行目で cleanup_srcu_struct() を呼んで後始末をするのは安全です。
D.1.3 実装
この節は、SRCUのデータ構造、初期化、後始末、リード側、そして更新側のプリミティブを説明します。
D.1.3.1 データ構造
図D.4に、SRCUのデータ構造を示します。図D.5はそれを図式的に表したものです。completed フィールドは、struct srcu が初期化されて以来のグレースピリオドの数です。図に示すように、その低オーダーのビットを struct srcu_struct_array のインデックスとして使います。per_cpu_ref フィードがその配列を指し、mutex フィードは、一つの synchronize_srcu() だけが進行できるために使います。
D.1.3.2 初期化の実装
図D.6は、SRCUの初期化関数、init_srcu_struct() です。この関数は単純にstruct srcu_struct のフィードを初期化し、成功ならゼロ、失敗なら -ENOMEM を返します。
図D.7は、SRCUの後始末関数です。主な後始末関数であるcleanup_srcu_struct() は、図の19から29行目です。しかし、それは、同じ図の13から17行目のsrcu_readers_active() を直ちに呼んで、この struct srcu_struct を現在使っているリーダーがいないことを確認します。
srcu_readers_active() 関数は単純に、両方のインデックスに対する srcu_readers_active_idx() の和を返し、srcu_readers_active_idx() は、1から11行目にあり、指定されたインデックスの CPUごとのカウンタの和を返します。
srcu_readers_active() の戻り値がゼロでない時は、cleanup_srcu_struct() は、24行目で警告を出して、25と26行目で単純に戻ります。struct srcu_struct はまだ使われているので、解放を拒否します。このような警告は常にバグを意味します。バグは報告されたので、システムはわずかなメモリリークをしたまま続行するのが良く、無理にそれを解放してメモリ破壊を起こす危険をおかすことはありません。
そうでない場合、cleanup_srcu_struct() は、27と28行目で CPUごとのカウンタ配列を解放して、ポインタを NULL にします。
D.1.3.3 リード側実装
図D.8は srcu_read_lock() を実装するコードです。この関数は、メモリバリアとアトミック操作の必要を避けるように、注意深く作られています。
5と11行目は、プリエンプションを無効にして、有効に戻します。単一のCPUで、コードのシーケンスがプリエンプトされないで実行するのを強制するためです。6行目でグレースピリオドカウンタの最下位のビットを拾い、これをCPUごとのカウンタのどちらをこのSRCUリード側クリティカルセクションのために使うかを示すのに使います。7行目の barrier() 呼び出しは、インデックスが一度だけフェッチされることを保証するコンパイラへのディレクティブです。なので、9行目で使われるインデックスは、12行目で返されるものと同じになります。
脚注。名前とは違って、barrier() は、CPUがコードとメモリアクセスの実行をリオーダーする能力には全く影響しないことに注意下さい。
8と9行目は現在CPUのその選択されたカウンタを加算します。
脚注。smp_processor_id() が、プリエンプションが禁止されている時に限って、長期間の意味を持つことに注意するのは重要です。プリエンプションが禁止されていないならば、このプリミティブの実行の直後にプリエンプションが起きて、以下のコードがどこか他のCPUで実行することもありえます。
10行目は以下の実行が、8と9行目の後に起きるのを強制します。CONFIG_PREEMPT がないビルドでは、それはコードが誤ってオーダーされるのを防ぎます。ただ、それは間に入る割り込みハンドラの見地からに限ります。CONFIG_PREEMPT のあるカーネルでは、必要とされる barrier() 呼び出しは、11行目の preempt_enable() に埋め込まれているので、その場合、srcu_barrier() は、no-op です。最後に、12行目はインデックスを返し、それが対応する srcu_read_unlock() で使えるようにします。
図D.9は、srcu_read_unlock() のコードです。同様に、3と7行目はプリエンプションを無効にして、有効に戻します。単一のCPUで、コードのシーケンス全体がプリエンプトされないで実行するのを強制するためです。CONFIG_PREEMPT カーネルでは、3行目の preempt_disable() は barrier()プリミティブを含み、そうでない場合、4行目で barrier() が与えられます。繰り返しますが、このディレクティブは以下のコードが、間に入る割り込みハンドラの見地から見て、クリティカルセクションの後に実行される事を強制します。5と6行目はこのCPUのカウンタを減算します。なお、対応する srcu_read_lock() で使われたのと同じインデックスを使います。
鍵となる点は、あるCPUのカウンタを他のCPUが見るためには、元のCPUの割り込みハンドラの助けが必要だということです。この割り込みハンドラが、カウンタを観察する前に、必要なメモリバリアが実行されることを保証する責任を持ちます。
D.1.3.4 更新側実装
SRCUの背後にある鍵となる点は、synchronize_sched() が、全ての現在実行しているプリエンプションが禁止されているコード部分が完了するまでブロックすることです。synchronize_sched() プリミティブは、図D.10に示すように、この効果を大いに使います。
5行目は、グレースピリオドカウンタのスナップショットを取ります。6行目で mutex を取ります。7から10行目で、少なくても2つのグレースピリオドがスナップショットから経過したかを見ます。そうならば、mutex を放して、戻ります。この場合、誰か他の人が、私達の仕事を代わりにやってくれたのです。そうでないなら、11行目で、srcu_read_lock() でグレースピリオドカウンタの加算された値を見る他のCPUはいずれも、このCPUが synchronize_srcu() に入る前に行った全ての変更を見ることを保証させます。これは、次のグレースピリオドをブロックしていない全ての SRCU リード側クリティカルセクションが全ての以前の変更を見る事を保証するために必要です。
12行目はグレースピリオドカウンタの最下位のビットを拾い、CPUごとのカウンタ配列のインデックスとして後で使います。そして、13行目はグレースピリオドカウンタを加算します。そして14行目は、全ての現在実行している srcu_read_lock() が完了するのを待ちます。なので、15行目に着いた時には、現在残っている全ての srcu_read_lock() インスタンスは、sp->completed からの更新された値を使っているはずです。なので、15行目の srcu_readers_active_idx() でサンプルされたカウンタは、単調減少することが保証され、それらの合計がゼロになったならば、ずっとそのままであることが保証されます。
しかし、srcu_read_unlock() にはメモリバリアプリミティブがありません。なので、CPUは、SRCUクリティカルセクションの中にカウンタ減算をリオーダーする権利があります。すると、SRCUで守られたデータ構造への参照は、実際には、SRCUクリティカルセクションから「漏れ出る」可能性があります。このシナリオは、17行目の synchronize_sched() で対策されます。それは、preempt_disable() コードシーケンス(srcu_read_unlock() にあったような)内で実行している他の全てのCPUがそのシーケンスを完了するまでブロックします。ある preempt_disable() コードシーケンスの完了は、そのシーケンスを実行中のCPUによって観察されますから、シーケンスの完了は、全ての以前のSRCUリード側クリティカルセクションの完了を意味します。必要な全てのメモリバリアは、観察をしているコードが提供します。
なので、この時点で、18行目が示すように、mutex を放して、呼び出し元に戻るのは安全です。呼び出し元は、同じ struct srcu_struct を共有する全てのSRCUリード側クリティカルセクションが、synchronize_srcu() を呼ぶ前に行われた全ての更新を見ることが保証されます。
クイッククイズD.3
synchronize_sched() で区切られた更新が、順に行われると考えて良いのはなぜですか?
クイッククイズD.4
なぜ、synchronize_srcu() (図D.10)の17行目は、18行目の mutex の解放より前にないといけないのですか?この2行を交換できるためには、何を変える必要がありますか?そのような変更は価値がありますか?なぜ?
D.1.4 SRCU まとめ
SRCUは、RCUに似たプリミティブのセットを提供し、それは、SRCUリード側クリティカルセクションで一般的なスリープを許します。しかし、SRCUがプロトタイプコードでしか使われたことのない事に注意するのは重要です。とはいえ、それは、RCU torture テストをパスしました。将来、SRCUが、使われるとしたら、どのような使われ方をするのかを見るのは、興味深いことです。
D.2 階層的RCUの概要
古典的RCUのリード側プリミティブは、優れた性能とスケーラビリティを楽しみますが、更新側プリミティブ、それはいつ既存のリード側クリティカルセクションが終わったかを判断します、はせいぜい数十のCPUだけを念頭に置いて設計されました。そのスケーラビリティは、それぞれのCPUがそれぞれのグレースピリオドの間に少なくても一度取らなくてはいけないグローバルロックによって制限されます。古典的RCUは実際に数百のCPUまでスケールしますし、ほぼ1000CPUまでスケールするようにいじることもできます(しかし、グレースピリオドが長くなる犠牲をはらいます)が、現れつつあるマルチコアシステムはそれがより良くスケールすることを要求します。
さらに、古典的RCUの dynticks インタフェースは最適とは言えません。その結果、古典的RCUは全てのCPUを少なくてもグレースピリオドに一度、起床します。これから起きる問題を見るために、16CPUシステムで、負荷が低いために4つのCPUだけがビジーであるものを考えましょう。完璧な世界では、
XXX
----ここから
----ここまで
D.2.2 古典的RCU実装の簡単な概説
古典的RCU実装の背後の鍵となる概念は、古典的RCUのリード側クリティカルセクションはカーネルコードに閉じ込められていて、ブロックすることを許されないことです。ということは、あるCPUがブロックしたり、アイドルループにいたり、あるいはカーネルを抜けることが見えたならば常に、そのCPUで以前に走っていた全てのリード側クリティカルセクションは完了していなければいけないということです。そのような状態は、「静止状態」と呼ばれ、それぞれのCPUが少なくても一つの静止状態を通り過ぎたならば、その後そのRCUグレースピリオドは終わります。
古典的RCUの最も重要なデータ構造は、rcu_ctrlblk 構造体で、->cpumask フィールドを持ちます。それは、図D.11に示すように、CPUごとに1ビットを持ちます。それぞれのCPUのビットはグレースピリオドの開始のたびに1に設定され、それぞれのCPUはそれが静止状態を通り過ぎた後、自分のビットをクリアしなくてはいけません。複数のCPUが自分のビットを同時にクリアしたいことがあると ->cpumask フィールドをこわしますから、->cpumask を守るために ->lock スピンロックが使われ、そのような破壊を防ぎます。不幸にも、このスピンロックは、数百以上のCPUがあると、極端な競合の被害を受けることがあります。それはマルチコアのトレンドが続けばすぐにとても一般的になるかもしれません。さらに悪いことに、全てのCPUが自分のビットをクリアしなくてはいけないという事実は、CPUがグレースピリオドの間はスリープすることを許されないということで、それはLinuxの省電力能力を制限します。
次の節は、新しいリアルタイムでないRCU実装に私達が必要なものを列挙します。
D.2.3 RCUの要求
リアルタイムRCUの要求の一覧 [MS05] は良い開始点です。
1 遅延破壊。このため、RCUグレースピリオドは、全ての既存のRCUリード側クリティカルセクションが完了するまで終わることはできません。
2 高信頼。RCUが何年にも渡って、 24x7 運用をサポートするように。
3 割り込みハンドラから呼ぶことができる。
4 有限のメモリフットプリント。このため、コールバックが多すぎる時には、グレースピリオドを早く終わらせる機構がある。(これは LCA2005 リストから弱められました)
5 メモリブロックから独立。RCUがどのようなメモリアロケータとも一緒に動けるように。
6 リード側は同期なし。このため、CPUあるいはタスクにローカルなメモリへの通常のアトミックでない命令だけが許されます。(これは LCA2005 リストから強められました)
7 無条件のリードからライトへのアップグレード。それは、更新側ロックがRCUリード側クリティカルセクション内で取られるLinuxカーネルのいくつかの場所で使われます。
8 互換なAPI
9 これはリアルタイムRCUになる予定はないので、プリエンプト可能RCUリード側クリティカルセクションのための要件は落とすことができます。しかし、過去数年の変化に対応するために、以下の新しい要件を追加する必要があります。
10 RCU内部のロック競合を極端に低くして得られるスケーラビリティ。RCUは少なくても優雅に1024CPUをサポートし、できれば、少なくても4096も。
11 エネルギー節約。RCUは低電力の dynticks-idle CPUを起こすのを避ける一方で、それでもなお現在のグレースピリオドがいつ終わるかを決めることができなくてはいけません。これはリアルタイムRCUで実装済みですが、真剣な単純化が必要です。
12 NMIハンドラや割り込みハンドラ内でもRCUリード側クリティカルセクションは許されなくてはいけません。プリエンプト可能RCUは、synchronize_sched() を別に実装するため、この要件を避けることができたことに注意下さい。
13 RCUは繰り返されるCPUホットプラグ操作があっても優雅に動作しなくてはいけません。これは古典的とリアルタイムRCUの両方が実現していた要件を単純に引き継いだだけです。
14 以前に登録された全てのRCUコールバックが完了するのを待つことができなくてはいけません。なおこれは、既に、rcu_barrier() の形で提供されています。
15 応答に失敗しているCPUを検出できれば望ましいです。それは、RCUと、RCUグレースピリオドが終わるのを妨げる可能性のあるいろいろな無限ループバグとハードウェア故障の診断を助けるでしょう。
16 RCUグレースピリオドを極端に短く切り上げる事ができれば望ましいです。最後の関連するRCUリード側クリティカルセクションが完了してから数百マイクロ秒以内にRCUグレースピリオドを強制的に完了させられるように。しかし、そのような操作は厳しいCPUオーバーヘッドを起こすことが予想されますから、それぞれがRCUグレースピリオドを待つ必要のある操作の長いシーケンスを実行する時に、主に便利でしょう。
新しい要件の中で最も差し迫ったものは、最初のもの、スケーラビリティです。なので次の節は、RCUの内部的ロックの競合を、一桁少なくするにはどうしたらいいかを説明します。
D.2.4 よりスケーラブルなRCU実装に向かって
ロック競合を減らす一つの有効な方法は、図D.12に示すように階層を作ることです。
----ここから
----ここまで
以上