ホームページ >システムチュートリアル >Linux >Linux カーネルの RCU メカニズムの詳細な説明

Linux カーネルの RCU メカニズムの詳細な説明

WBOY
WBOY転載
2024-02-10 21:09:111351ブラウズ

Linux カーネルは、プロセス スケジューリング、メモリ管理、デバイス ドライバー、ネットワーク プロトコルなど、さまざまな同時実行性の問題に対処する必要がある複雑なシステムです。データの一貫性と正確性を保証するために、Linux カーネルはスピン ロック、セマフォ、読み取り/書き込みロックなどのさまざまな同期メカニズムを提供します。ただし、これらの同期メカニズムには次のようないくつかの欠点があります。

Linux カーネルの RCU メカニズムの詳細な説明

    スピン ロックは、CPU のビジー待機で時間を無駄にするため、プリエンプティブ カーネルでは使用できません。
  • セマフォによりプロセスがスリープしたりウェイクアップしたりするため、コンテキスト切り替えのオーバーヘッドが増加します。
  • 読み取り/書き込みロックはライターの飢餓またはリーダーの飢餓を引き起こし、ライターよりもリーダーの数が多い場合、ライターはロックを取得するためのオーバーヘッドも支払う必要があります。
それでは、より優れた同期メカニズムはあるのでしょうか?答えは「はい」です。それは RCU (Read Copy Update) です。 RCU は、パブリッシュ/サブスクライブ モデルに基づく同期メカニズムであり、効率的な読み取り操作と低遅延の更新操作を実現できます。 RCU の基本的な考え方は次のとおりです:

    読み取り操作ではロックを取得する必要はなく、rcu_read_lock() と rcu_read_unlock() を使用してクリティカル エリアをマークするだけで済みます。
  • 更新操作では、まずデータのコピーを作成し、コピーに変更を加え、次に rcu_assign_pointer() を使用して新しいデータをパブリッシュし、call_rcu() または synchronize_rcu() を使用してすべての読み取り操作が完了するのを待ってから、データをリサイクルする必要があります。古いデータ。
  • RCU の利点は何ですか?次のような特徴があります:

RCU は、ロックの競合とコンテキストの切り替えを削減し、同時実行パフォーマンスとシステムの応答性を向上させることができます;

  • RCU はデッドロックや優先度の逆転などの問題を回避し、プログラミング モデルを簡素化します;
  • RCU は、リアルタイム システム、NUMA システム、SMP システムなどのさまざまなシナリオに適応できます。;
  • RCU は、スピン ロック、アトミック操作などの他の同期メカニズムと組み合わせて使用​​できます。
  • それでは、RCU はどのように機能するのでしょうか?どのような応用シナリオがありますか?この記事では、RCU の効率的な同期メカニズムを原理と応用の 2 つの側面から紹介します。 RCU の設計思想は比較的明確で、古いポインターと新しいポインターを置き換えることによってロックフリーの共有保護が実現されます。しかし、コードレベルになると、まだ理解するのがやや困難です。 「Linux デバイス ドライバー カーネル メカニズムの詳細」の第 4 章では、RCU の背後で従うルールが非常に明確に説明されています。コード分析が簡単すぎると思うため、これらのルールは比較的高い視点から見られています。読者に理解してもらいましょう細部に迷う。最近この本を手に入れてから、もう一度RCU部分の文章をじっくり読んだのですが、本に書くにはふさわしくない内容もあるかもしれないので、もう少し内容を加えるべきだと感じました。
RCU 読み取り側がクリティカル セクションに入ったことを示す兆候は、rcu_read_lock を呼び出すことです。この関数のコードは次のとおりです:

リーリー

この実装には 3 つの関数呼び出しがあるように見えますが、実際の作業は最初の関数 __rcu_read_lock() によって行われます。__rcu_read_lock() は preempt_disable() を呼び出してカーネルのプリエンプティビリティをオフにします。ただし、割り込みは許可されます。リーダーが rcu クリティカル セクションにいて、共有データ領域のポインター p を読み取ったばかりであるとします (ただし、p 内のデータ メンバーにはまだアクセスしていません)。割り込みが発生し、割り込み処理の例は次のとおりです。 ISR は、p が指すデータ領域を変更する必要がある場合があります。RCU の設計原則に従って、ISR は同じサイズのデータ​​領域 new_p を新たに割り当て、古いデータ領域 p のデータを新しいデータ領域にコピーします。データ領域、次に new_p 基本的に、ISR の後にデータ変更作業を実行します (new_p 空間で変更されるため、p への同時アクセスはなく、RCU はロックフリーのメカニズムであり、これが理由です)。データ更新作業を完了し、new_p を p に代入し (p=new_p)、最後にコールバック関数を登録して、適切なタイミングで古いポインタ p を解放します。したがって、古いポインタ p への参照がすべて終了している限り、p を解放しても問題はありません。割り込み処理ルーチンがこれらのタスクの完了から戻ったとき、割り込まれたプロセスは引き続き p 空間のデータ、つまり古いデータにアクセスしますが、この結果は RCU メカニズムによって許可されています。

#RCU ルールでは、リーダーとライター間のポインターの切り替えによって発生する一時的なリソース ビューの不一致が許容されます。

RCU に関する次の興味深い質問は、古いポインタはいつ解放されるのかということです。これに対する答えは、多くの本で見てきましたが、「システム内のすべてのプロセッサでプロセスの切り替えが発生したとき」です。この様式化された答えは、RCU メカニズムを初めて使用する読者を混乱させることがよくありますが、コールバック関数を呼び出して古いポインタを解放する前に、すべてのプロセッサでプロセスの切り替えが発生するのをなぜ待たなければならないのでしょうか?これは実際には RCU の設計ルールによって決定されます。 古いポインターへのすべての参照は、rcu_read_lock および rcu_read_unlock に含まれるクリティカル セクションでのみ発生でき、このクリティカル セクションではプロセスの切り替えはできません, Onceクリティカル セクションがクリティカル セクションの外にある場合、古いポインター p へのいかなる形式の参照も存在しないはずです。プロセスが切り替わると、古いポインタを解放するコールバック関数が呼び出され、古いポインタが解放される可能性があるため、このルールでは、リーダーがクリティカル セクションでプロセスを切り替えることができないことが明らかに要求されます。再スケジュールされると、解放されたメモリ領域を参照する可能性があります。

rcu_read_lock がカーネル プリエンプティビリティをオフにするだけでよい理由がわかりました。クリティカル セクションで割り込みが発生した場合でも、現在のプロセスを切り替えたり削除したりすることが不可能になるからです。 カーネル開発者、というか RCU 設計者ができることは限られています 。次のステップはユーザーの責任ですが、RCU のクリティカル セクションで関数が呼び出された場合、関数がスリープする可能性があり、RCU の設計ルールに違反し、システムが不安定な状態になります。

このことからも、何かを利用するにはその内部の仕組みを理解する必要があることが改めて分かります 先ほどの例のように、今はプログラムに問題がなくても、システムには潜在的な危険性が残されているのです。時限爆弾: いつでも爆発する可能性があり、特に長い時間が経った後に突然問題が発生した場合にはそうです。ほとんどの場合、問題を見つけるのにかかる時間は、落ち着いて RCU の原則を注意深く理解するのにかかる時間よりもはるかに長い場合があります。

RCU のリーダーは、rwlock のリーダーよりも自由度が高くなります。 RCU リーダーは、共有リソースにアクセスするときにライターの感情を考慮する必要がないため、rwlock ライターとは異なり、rwlock リーダーは、共有リソースを読み取るときに、ライターがリソースを操作していないことを確認する必要があります。 この 2 つの違いは、RCU ではリーダーとライターの間で共有リソースが分離されているのに対し、rwlock のリーダーとライターは最初から最後まで共有リソースのみを使用することに由来します。これは、RCU のライターがより多くの責任を負わなければならず、同じ共有リソースを更新する複数のライター間で何らかの相互排他メカニズムを導入する必要があることも意味します。そのため、RCU は「ロックフリー メカニズム」です。このステートメントはリーダーと読者に限定されます。作家たち。したがって、読み取り操作が多数あり、更新操作が比較的少ない状況では、RCU メカニズムを使用する必要があることがわかります。現時点では、RCU の読み取り操作には他のロック メカニズムと比べてロック オーバーヘッドがほとんどないため、RCU はシステム パフォーマンスを大幅に向上させることができます。

実際の使用では、共有リソースはリンク リストの形式で存在することがよくあります。カーネルは、RCU モードでのリンク リスト操作用のいくつかのインターフェイス関数を実装しています。リーダーとユーザーは、list_add_tail_rcu、list_add_rcu、hlist_replace_rcu などのこれらのカーネル関数を使用する必要があります。具体的な使用方法については、カーネル プログラミングまたはデバイス ドライバーの情報を参照してください。

古いポインターの解放に関して、Linux カーネルはユーザーが使用できる 2 つの方法を提供します。1 つは call_rcu を呼び出す方法、もう 1 つは synchronize_rcu を呼び出す方法です。前者は非同期メソッドです。call_rcu は、古いポインタをノードに解放するコールバック関数を配置し、現在 call_rcu を実行しているプロセッサのローカル リンク リストにノードを追加します。クロック割り込みの Softirq 部分 (RCU_SOFTIRQ ) 、rcu ソフト割り込み処理関数 rcu_process_callbacks は、現在のプロセッサがスリープ期間 (カーネル プロセスのスケジューリングやその他の側面を含む静止状態) を経験したかどうかを確認します。rcu のカーネル コード実装は、システム内のすべてのプロセッサがスリープ期間を経験したかどうかを判断します。期間 (すべてのプロセッサでプロセスの切り替えが発生したため、この時点で古いポインタを安全に解放できることを意味します) が経過すると、call_rcu によって提供されるコールバック関数が呼び出されます。

synchronize_rcu の実装は待機キューを利用します。実装中に、call_rcu のように現在のプロセッサのローカル リンク リストにノードも追加されます。call_rcu との違いは、このノードのコールバック関数が wakeme_after_rcu であり、次に synchronize_rcu であることです。システム内のすべてのプロセッサでプロセスの切り替えが発生するまで、wakeme_after_rcu は待機キューでスリープし、スリープしている synchronize_rcu をウェイクアップするために rcu_process_callbacks によって呼び出されます。ウェイクアップされた後、synchronize_rcu は古いポインタを解放できることを認識します。

したがって、登録されたコールバック関数は call_rcu が戻った後に呼び出されなかった可能性があることがわかります。これは、古いポインターが解放されていないことを意味し、古いポインターは synchronize_rcu が戻った後に解放されている必要があります。したがって、call_rcu を呼び出すか synchronize_rcu を呼び出すかは、特定のニーズと現在のコンテキストに依存します (たとえば、synchronize_rcu 関数は割り込み処理のコンテキストでは使用できません)。

この記事では、Linux カーネルの効率的な同期メカニズムである RCU について紹介します。これは、パブリッシュ/サブスクライブ モデルに基づく同期メカニズムです。 RCU の基本的な考え方、主要なインターフェイス、実装の詳細を原理的な側面から分析し、対応するコード例を示しました。また、アプリケーションの観点から、リンク リスト操作、タイマー管理、ソフト割り込み処理、その他のシナリオでの RCU の使用法を紹介し、対応するコード例を示しました。この記事を学習することで、RCU の基本的な使用法をマスターし、実際の開発で RCU を柔軟に使用して効率的な同期要件を実現できるようになります。この記事がお役に立てば幸いです!

以上がLinux カーネルの RCU メカニズムの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlxlinux.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。