私はロバート・ラブの次の記事を読んでいます
http://www.linuxjournal.com/article/6916
それは言う
「...ワークキューがプロセスコンテキストで実行されるという事実について説明しましょう。これは、すべて割り込みコンテキストで実行される他の下半分のメカニズムとは対照的です。割り込みコンテキストで実行されているコードは、スリープまたはブロックできません。コンテキストには、再スケジュールするためのバッキングプロセスがありません。したがって、割り込みハンドラはプロセスに関連付けられていないため、スケジューラがスリープ状態にするためのものはなく、さらに重要なことに、スケジューラがウェイクアップするためのものはありません... "
わかりません。 AFAIK、カーネルのスケジューラはO(1)で、ビットマップを介して実装されています。それでは、schedulerが割り込みコンテキストをスリープ状態にして、次のスケジュール可能なプロセスを取得して制御を渡すのを止めるのは何ですか?
それはデザインのアイデアだと思います。
もちろん、割り込みでスリープできるシステムを設計できますが、システムを理解しにくく複雑にすることを除いて(考慮しなければならない多くの状況の多く)、それは何の助けにもなりません。したがって、デザインビューから、スリープできない割り込みハンドラを宣言することは非常に明確で、実装が簡単です。
Robert Love(カーネルハッカー)から: http://permalink.gmane.org/gmane.linux.kernel.kernelnewbies/1791
割り込みにはバッキングプロセスコンテキストがないため、割り込みハンドラでスリープすることはできません。言い換えると、割り込みハンドラーはタスクに関連付けられていないため、「スリープ状態にする」ことはなく、(さらに重要なことに)「ウェイクアップするものもありません」。それらはアトミックに実行する必要があります。
これは他のオペレーティングシステムと異なりません。ほとんどのオペレーティングシステムでは、割り込みはスレッド化されません。しかし、下半分はしばしばそうです。
ページフォールトハンドラーがスリープできるのは、プロセスコンテキストで実行されているコードによってのみ呼び出されるためです。カーネル自体のメモリはページングできないため、ユーザー空間のメモリアクセスのみがページフォールトを引き起こす可能性があります。したがって、いくつかの特定の場所(copy_ {to、from} _user()の呼び出しなど)のみがカーネル内でページ違反を引き起こす可能性があります。これらの場所はすべて、スリープ可能なコード(つまり、プロセスコンテキスト、ロックなしなど)で作成する必要があります。
それでは、schedulerが割り込みコンテキストをスリープ状態にして、次のスケジュール可能なプロセスを取得して制御を渡すのを止めるのは何ですか?
問題は、割り込みコンテキストがプロセスではないため、スリープ状態にできないことです。
割り込みが発生すると、プロセッサはレジスタをスタックに保存し、割り込みサービスルーチンの先頭にジャンプします。これは、割り込みハンドラーが実行されている場合、割り込みが発生したときに実行されていたプロセスのコンテキストで実行されていることを意味します。割り込みはそのプロセスのスタックで実行されており、割り込みハンドラが完了すると、そのプロセスは実行を再開します。
割り込みハンドラー内でスリープまたはブロックしようとすると、割り込みハンドラーを停止するだけでなく、割り込みハンドラーが停止したプロセスも終了します。割り込みハンドラは、中断されたプロセスが何をしていたのか、またはそのプロセスが中断されても安全であるとしても、知る方法がないので、これは危険かもしれません。
物事がうまくいかない可能性のある単純なシナリオは、割り込みハンドラとそれが割り込みするプロセスの間のデッドロックです。
この時点で、デッドロックがあります。 Process1 ISRがスタックで完了するまで実行を再開できません。しかし、ISRはProcess1が解放されるのを待ってブロックされますLockA。
スレッドスイッチングインフラストラクチャはその時点では使用できないためです。割り込みを処理する場合、より高い優先度のものだけが実行できます- 割り込み、タスク、およびプロセッサの優先度に関するインテルソフトウェア開発者向けマニュアル を参照してください。別のスレッドの実行を許可した場合(質問でそれが簡単であることを意味します)、何も実行させることができなくなります。ページ違反が発生した場合は、サービスを使用する必要があります。割り込みが処理されている間は使用できないカーネル内(理由については以下を参照)。
通常、割り込みルーチンの唯一の目標は、デバイスに割り込みを停止させ、低い割り込みレベルで何かをキューに入れることです(UNIXでは、これは通常、非割り込みレベルですが、Windowsでは、ディスパッチ、apc、またはパッシブレベルです)。カーネル/ OSのより多くの機能にアクセスできる場所で、面倒な作業を行います。 - ハンドラーの実装 を参照してください。
これは、O/Sが機能する方法の特性であり、Linuxに固有のものではありません。割り込みルーチンはいつでも実行できるので、割り込みの状態に一貫性がありません。スレッドスケジューリングコードを中断した場合、その状態に一貫性がないため、「スリープ」してスレッドを切り替えることができるかどうか確信が持てません。スレッド切り替えコードが中断されないように保護している場合でも、スレッド切り替えはO/Sの非常に高レベルの機能であり、スレッドが依存するすべてのものを保護している場合、割り込みは、その名前が暗示する命令よりも示唆になります。
それでは、schedulerが割り込みコンテキストをスリープ状態にして、次のスケジュール可能なプロセスを取得して制御を渡すのを止めるのは何ですか?
スケジューリングはタイマー割り込みで発生します。基本的なルールは、一度に1つの割り込みしか開くことができないため、「デバイスXからデータを取得」割り込みでスリープ状態に入ると、タイマー割り込みを実行してそれをスケジュールすることはできません。
割り込みも何度も発生してオーバーラップします。 「データを取得」割り込みをスリープ状態にして、さらに多くのデータを取得すると、どうなりますか?混乱を招く(そして壊れやすい)ため、キャッチオールルールは次のとおりです:割り込みでスリープしない。あなたはそれを間違えるでしょう。
ISRをスリープさせることができたとしても、それを行う必要はありません。 ISRをできるだけ高速にして、後続の割り込みを見逃すリスクを軽減したいとします。
本質的に、問題は、割り込みハンドラーで有効な「現在」(現在のプロセスtask_structureへのアドレス)を取得できるかどうかです。「はい」の場合、それに応じてコンテンツを変更して、「スリープ」状態にすることができます。状態が何らかの形で変更された場合は、後でスケジューラによって戻されます。答えはハードウェアに依存する場合があります。
ただし、ARMでは、「現在の」は割り込みモードでの処理には無関係であるため、不可能です。以下のコードを参照してください。
#linux/Arch/arm/include/asm/thread_info.h
94 static inline struct thread_info *current_thread_info(void)
95 {
96 register unsigned long sp asm ("sp");
97 return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
98 }
uSERモードとSVCモードのspは「同じ」です(ここで「同じ」とは、それらが等しいことを意味するのではなく、ユーザーモードのspがユーザースペーススタックを指すのに対し、svcモードのsp r13_svcは、ユーザープロセスのカーネルスタックを指します。 task_structureは前のタスクスイッチで更新されました。システムコールが発生すると、プロセスは再びカーネルスペースに入ります。sp(sp_svc)がまだ変更されていない場合、これらの2つのspは互いに関連付けられています。つまり、これらは同じです。 ')、SVCモードでは、カーネルコードは有効な' current 'を取得できます。しかし、他の特権モード、たとえば割り込みモードでは、spは「異なる」ので、cpu_init()で定義された専用アドレスを指します。これらのモードで計算された「現在の」は中断されたプロセスとは無関係であり、それにアクセスすると予期しない動作が発生します。そのため、システムコールはスリープできるが割り込みハンドラーはスリープできないと常に言われています。システムコールはプロセスコンテキストで機能しますが、割り込みはできません。
割り込みハンドラによるブロックを禁止することは、設計上の選択です。デバイスにデータが存在する場合、割り込みハンドラーは現在のプロセスをインターセプトし、データの転送を準備して割り込みを有効にします。ハンドラーが現在の割り込みを有効にする前に、デバイスはハングアップする必要があります。 I/Oをビジー状態に保ち、システムの応答性を維持したいので、割り込みハンドラをブロックしない方がよいでしょう。
「不安定な状態」は本質的な理由ではないと思います。プロセスは、ユーザーモードまたはカーネルモードに関係なく、割り込みによって割り込まれる可能性があることに注意する必要があります。カーネルモードのデータ構造に割り込みハンドラと現在のプロセスの両方がアクセスし、競合状態が存在する場合、現在のプロセスはローカル割り込みを無効にする必要があります。さらに、マルチプロセッサアーキテクチャの場合は、クリティカルセクションでスピンロックを使用する必要があります。 。
また、割り込みハンドラーがブロックされている場合は、起動できません。 「ブロック」とは、基本的には、ブロックされたプロセスが何らかのイベント/リソースを待機していることを意味します。そのため、そのイベント/リソースの待機キューにリンクされます。リソースが解放されるたびに、解放プロセスは待機中のプロセスをウェイクアップします。
ただし、本当に煩わしいのは、ブロックされたプロセスがブロック時間中に何もできないことです。それは不公平であるこの罰に対して何も悪いことをしませんでした。そして、誰も確実にブロッキング時間を予測できなかったので、無実のプロセスは不明確な理由と無制限の時間を待たなければなりません。
Linuxカーネルには、割り込みスタックを割り当てる2つの方法があります。 1つは中断されたプロセスのカーネルスタックにあり、もう1つはCPUごとの専用の割り込みスタックです。割り込みコンテキストがCPUごとに専用の割り込みスタックに保存されている場合、実際には、割り込みコンテキストはどのプロセスにも完全には関連付けられていません。 「現在の」マクロは、現在実行中のプロセスへの無効なポインタを生成します。これは、一部のアーキテクチャの「現在の」マクロはスタックポインタで計算されるためです。割り込みコンテキストのスタックポインタは、一部のプロセスのカーネルスタックではなく、専用の割り込みスタックを指す場合があります。
高レベルの割り込みハンドラは、システムタイマー割り込みの操作を含む、優先度の低いすべての割り込みの操作をマスクします。したがって、割り込みハンドラーは、それをスリープ状態にする可能性のあるアクティビティーに自分自身が関与することを回避する必要があります。ハンドラーがスリープ状態になると、タイマーがマスクされ、スリープ状態のスレッドをスケジュールできないため、システムがハングすることがあります。これは理にかなっていますか?
より高いレベルの割り込みルーチンが、一定の時間が経過すると次に実行する必要があるポイントに到達した場合、別の割り込みルーチンを実行するように要求をタイマーキューに入れる必要があります(低い優先度で)レベル)しばらくしてから。
その割り込みルーチンが実行されると、優先順位レベルが元の割り込みルーチンのレベルに戻り、実行が継続されます。これは睡眠と同じ効果があります。
Linux OSでの設計/実装の選択にすぎません。この設計の利点は単純ですが、リアルタイムOSの要件には適さない場合があります。
他のOSには他の設計/実装があります。
たとえば、Solarisでは、割り込みの優先順位が異なる場合があり、ほとんどのデバイスの割り込みを割り込みスレッドで呼び出すことができます。割り込みスレッドは、スレッドのコンテキストで各割り込みスレッドに個別のスタックがあるため、スリープを許可します。割り込みスレッドの設計は、割り込みよりも優先度の高いリアルタイムスレッドに適しています。