ハードウェア割り込みまたはソフトウェア割り込みによってトラップされるカーネルモードでのコンテキストスイッチングは間違いありません。コンテキストスイッチングはアトミックに保つ必要があることも知られていますが、アトミック性をどのように実装するのでしょうか。割り込みゲートはすべての割り込みを無効にすることが知られています(NMIが含まれているかどうかはわかりません)。割り込みゲート自体は自然にアトミックシーケンスと見なすことができますか?
2 に答える
アトミック操作は、次のようにカーネルに実装されています。高レベル (デバイス ドライバー開発者の POV など) では、カーネルは、ユーザー空間のミューテックスと同様に取得および解放されるロックを提供します。下位レベルでは、これらのロックは、アトミック操作の組み合わせを使用して実装され、プリエンプションが発生してはならないことをカーネル スケジューラに通知します。
スケジューラ自体では、割り込みをマスクすることで原子性が保証されます。これは単一の命令 (cli または sti) を使用して行われるため、それ自体がアトミックです。割り込みがクリアされている間に NMI が実際に発生する可能性がありますが、これは特殊なケースです。NMI ハンドラーは、奇妙なコンテキストで呼び出される可能性があることを認識しているため、コンテキストを変更しないようにします。
コンテキストの切り替えと相互排除。コンテキストの切り替えとは、タスクの切り替えを意味すると思います。正直なところ、タスクの切り替えは必要なく、ロックする必要がないことに注意する必要があります。ほとんどの場合、スケジューリング自体は、スケジューリングの決定を行うために必要な一部のデータへのアクセスに相互排除を必要としますが、スケジューリングの決定に従うコンテキストスイッチ自体は必要ありません。
ほとんどすべての最新のオペレーティング システムは、システム内の各スレッドが 2 つのスタックを保持する設計に従います。1 つはユーザー モード側に、もう 1 つはカーネル モード側にあります。タスクの切り替えに必要なのは、次の 3 ステップのアクションだけです。
- CPU を離れるタスクのスタックに CPU の状態 (すべてのレジスタ) を保存します。
- アクティブなスタックを切り替える
- 現在のタスク記述子 (CPU を離れるタスク) に実際のスタック ポインターを保存します。
- 現在のタスク記述子へのポインターを切り替える
- 現在のタスク記述子 (CPU に到着するタスク) から新しいスタック ポインターをスタック ポインター レジスターにインストールします。
- CPU の状態をスタック (CPU に到着したタスクのスタック) から復元します。
ご覧のとおり、コンテキスト スイッチは本来、タスクに対してローカルでありグローバルではない 2 つのデータ セットで動作します。CPU レジスタの内容は、共有データの例ではありません。ノート!カーネルにコードが 1 つしかない場合、コンテキスト切り替えを実装すると、すべてのコンテキスト切り替えが同じコードを通過します。これにより、タスクが切り替えられた場合、そのタスクのカーネル スタックの最上位にすべての CPU 状態が適切に定義された形式で含まれることが保証されます。そして、タスクがスイッチインされるたびに、カーネル スタックの一番上にある CPU 状態データを見つけることができます!
2 つのメモが存在します。
- CPU 上のほとんどすべての命令は、別のプロセッサとデータを共有しない場合、アトミックとして実行されることに注意してください。つまり、命令の実行を途中で中断することはできません。このため、現在のタスク記述子へのポインターの切り替えは、レジスター・ベースのオペランドを使用した xchg 命令によって安全に行うことができます。
- タスクの切り替えは、ハードウェアからの割り込みによって中断される可能性があります。ただし、割り込みハンドラはタスク コンテキストに依存しません。これにより、タスクに関して安全にタスクの切り替えを先取りし、それ自体に関しては、通常はスタック マシンの設計アプローチを使用して CPU を使用します。
カーネルでの相互排除。一般的に言えば、ケネルはハードウェア主導とソフトウェア主導の 2 つの部分に分けることができます。最初のものには、ハードウェア デバイス (割り込みハンドラー) によってトリガーされる割り込みによって呼び出すことができ、現在実行中のスレッドに依存せず、現在実行中のタスクのコンテキストに何らかの影響を与えることに依存しないコードが含まれます。 1 つは、現在実行中のスレッド (システム コールと例外ハンドラー) によって明示的または暗黙的に呼び出されるコードを含み、通常はタスク記述子データへのアクセスを必要とします。
この 2 つのカーネル部分は、データ ロックの異なる要件を提供します。カーネルのソフトウェア主導の部分でのみ使用されるデータは、カーネル環境によって提供される既知の同期プリミティブを使用できます。チェック アンド ウェイト アプローチに従うプリミティブを意味します。たとえば、ミューテックス。必要なデータがロックされている場合、タスクは自分自身を待ち行列に登録し、別のタスクのために CPU を解放できます。
ハードウェア駆動部分 (特定の 1 つの割り込みハンドラーのみ) でのみ使用できるデータは、ハンドラーの瞬間までに同じ時間の割り込みが処理されている場合、次の割り込みが CPU に配信されないという事実に依存できます。割り込み処理が完了したことを割り込みコントローラーに通知します (いわゆる EOI (End Of Interrupt) 通知)。これにより、1 つの割り込みハンドラのみが使用するデータと、割り込みハンドラの実行開始から EOI 通知の送信までの間に使用されるデータは自然な方法で保護され、追加のロックは必要ありませんでした。
最後に、ソフトウェア駆動型とハードウェア駆動型のカーネル部分の間、または異なる優先度の割り込みハンドラーの間で共有されるデータは、相互排除の実装に最も厳しい要件を提供します。このようなデータは、チェック待機ロックでも、同じ優先度の割り込み配信のシリアルな性質によっても保護できません。このようなデータのロック要件を暗示する主な要因が 2 つあります。
- CPU で実行されている現在のアクティビティは、任意の予測不可能な時間内の実行の任意の時点で、割り込みハンドラーによってプリエンプトされる可能性があります。つまり、ハードウェア割り込み処理は完全な非同期プロセスです。
- ハンドラーはリソースの解放を待つことができません。割り込みハンドラーでリソースの解放を待機しようとすると、リソースの解放を待機している割り込みハンドラーと、同時にソフトウェア駆動のカーネル部分の実行をブロックするタスクと、リソースを所有し、ソフトウェア駆動の一部として実行をブロックするタスクとの間でデッドロックが発生する可能性があります。リソースを解放するカーネル部分。
このため、このような場合、次の同期テクニックが使用されます。
- アトミック操作を使用します。説明されているコンテキストでは、ほとんどの各 CPU 命令はアトミックと見なすことができることに注意してください。通常、アトミック操作という用語は、「ロック」接頭辞が付いたプロセッサ命令を意味しますが、ロックはマルチプロセッサ システムの場合にのみ必要です。これは、同じメモリ セルへの一貫性のない物理的な並列アクセスを防ぐためです。
- 待機のないアルゴリズムとデータ構造を使用します。
- ISR の代わりに IST を使用します。この設計アプローチでは、割り込みハンドラーで実行する必要がある唯一の作業は、割り込み処理スレッドの実行をスケジュールし、割り込みについて通知することであると想定しています。これにより、一方では割り込みハンドラで実行されるコードの量と必要なロックの量が大幅に削減されます。一方、ISR から IST に移動されたコードは、制限なしでロックを使用できます。
- 割り込みロック要件の主な要因の 1 つであるプリエンプションを攻撃する、最も一般的で、最も一般的で、最も一般的な方法です。割り込みの受け付けを禁止することでプリエンプションを防止することができます。CPU は通常、割り込みを無効にする何らかの方法をサポートしています (たとえば、x86 プロセッサは、CLI (割り込みを無効にする) と STI (割り込みを有効にする) という 2 つの特別な命令を提供します)。CPUがそのような機能を提供しない場合、通常、割り込みコントローラー側でも割り込みを無効にすることができます(異なるRISCプロセッサーの場合だと思います)。割り込みを無効にするということは、プロセッサが割り込みコントローラーからシグナルを受け取ることができず、スレッドの切り替えが (少なくとも暗黙のうちに) 行われないため、どの割り込みハンドラーもコードの保護されたセクションの実行を先取りしないということの両方を意味します。通常、スケジューラをトリガーするタイマーは、他のすべてのデバイスのように割り込みを配信できないためです。この同期方法は、カーネルの実装には確かに必要ですが、あまりにも残忍です。ノート!システム内のすべての割り込みを無効にすることで同期を実現します。これは、割り込み処理の待ち時間とカーネルの応答性に悪影響を及ぼします。このため、カーネル開発者は、このような形式のクリティカル セクションをできるだけ少なくし、できるだけ短くしようとします。
割り込みゲートについて。はい、あなたは正しいです。Intel プロセッサは、割り込みゲートに入るときに割り込みを自動的に無効にし、ハンドラーからの iret で再び有効にします。このため、割り込みハンドラー全体がアトミックな方法で実行されていると見なすことができます。しかし!上記の数行で説明したように、人々はそのような方法で保護されるコードの量を最小限に抑えようとします。その結果、OS カーネルがトラップ ゲートの代わりに割り込みゲートを使用している場合でも、割り込みハンドラーでできるだけ早く手動で割り込みを再度有効にしようとします。
NMI は非常に特殊なケースです。それは通常、全世界が崩壊したことを意味します。すべてのシステムがすでにダウンしているときに同期を気にする人はいますか?