76

この質問の助けを借りて、知識のギャップを学び、埋めたいと思います。

したがって、ユーザーはスレッド(カーネルレベル)を実行しており、現在呼び出していますyield(システムコールだと思います)。スケジューラは、現在のスレッドのコンテキストを TCB (カーネルのどこかに格納されている) に保存し、実行する別のスレッドを選択して、そのコンテキストをロードし、そのCS:EIP. 物事を絞り込むために、私は x86 アーキテクチャ上で動作する Linux に取り組んでいます。それでは、詳細を説明したいと思います。

したがって、最初にシステムコールがあります。

1) のラッパー関数はyield、システム コールの引数をスタックにプッシュします。リターン アドレスをプッシュし、システム コール番号をレジスタにプッシュして割り込みを発生させます (たとえばEAX)。

2) 割り込みは、CPU モードをユーザーからカーネルに変更し、割り込みベクタ テーブルにジャンプし、そこからカーネル内の実際のシステム コールにジャンプします。

3) スケジューラーが呼び出され、現在の状態を TCB に保存する必要があると思います。これが私のジレンマです。スケジューラーは、その操作を実行するためにユーザースタックではなくカーネルスタックを使用するため (つまり、SS変更SPする必要があります)、プロセス内のレジスターを変更せずにユーザーの状態をどのように保存しますか? 状態を保存するための特別なハードウェア命令があることをフォーラムで読みましたが、スケジューラはどのようにそれらにアクセスし、誰がいつこれらの命令を実行しますか?

4) スケジューラは、状態を TCB に保存し、別の TCB をロードします。

5) スケジューラが元のスレッドを実行すると、制御がラッパー関数に戻り、スタックがクリアされ、スレッドが再開されます。

副次的な質問: スケジューラはカーネルのみのスレッド (つまり、カーネル コードのみを実行できるスレッド) として実行されますか? 各カーネル スレッドまたは各プロセスに個別のカーネル スタックはありますか?

4

3 に答える 3

124

大まかに言うと、理解すべきメカニズムが 2 つあります。1 つ目は、カーネルの開始/終了メカニズムです。これは、単一の実行中のスレッドを、ユーザーモード コードの実行から、そのスレッドのコンテキストでのカーネル コードの実行に切り替え、またその逆に切り替えます。2 つ目は、コンテキスト スイッチ メカニズム自体です。これは、カーネル モードで、あるスレッドのコンテキストでの実行から別のスレッドのコンテキストでの実行に切り替えます。

したがって、スレッド A が呼び出しsched_yield()てスレッド B に置き換えられると、次のようになります。

  1. スレッド A がカーネルに入り、ユーザー モードからカーネル モードに変わります。
  2. カーネル内のスレッド A は、カーネル内のスレッド B にコンテキスト スイッチします。
  3. スレッド B はカーネルを終了し、カーネル モードからユーザー モードに戻ります。

各ユーザー スレッドには、ユーザー モード スタックとカーネル モード スタックの両方があります。スレッドがカーネルに入ると、ユーザー モード スタックの現在の値 ( SS:ESP) と命令ポインター ( CS:EIP) がスレッドのカーネル モード スタックに保存され、CPU はカーネル モード スタックに切り替わりint $80ます。 CPU自体によって行われます。その後、残りのレジスタ値とフラグもカーネル スタックに保存されます。

スレッドがカーネルからユーザー モードに戻ると、カーネル モード スタックからレジスタ値とフラグがポップされ、カーネル モード スタックに保存された値からユーザー モード スタックと命令ポインター値が復元されます。

スレッドがコンテキストを切り替えると、スケジューラが呼び出されます (スケジューラは別のスレッドとして実行されるのではなく、常に現在のスレッドのコンテキストで実行されます)。スケジューラ コードは、次に実行するプロセスを選択し、switch_to()関数を呼び出します。この関数は基本的にカーネル スタックを切り替えるだけです。スタック ポインターの現在の値を現在のスレッド ( struct task_structLinux で呼び出される) の TCB に保存し、以前に保存したスタック ポインターを次のスレッドの TCB から読み込みます。この時点で、カーネルによって通常は使用されない他のスレッド状態 (浮動小数点/SSE レジスタなど) も保存および復元されます。切り替えられるスレッドが同じ仮想メモリ空​​間を共有しない場合 (つまり、それらが異なるプロセスにある場合)、ページ テーブルも切り替えられます。

したがって、スレッドのコア ユーザー モード状態は、コンテキスト スイッチ時に保存および復元されないことがわかります。カーネルに出入りするときに、スレッドのカーネル スタックに保存および復元されます。コンテキスト スイッチのコードは、ユーザー モード レジスタの値を上書きすることを心配する必要はありません。これらの値は、その時点までにカーネル スタックに安全に保存されています。

于 2012-10-03T05:20:31.167 に答える
13

手順2で見逃したのは、スタックがスレッドのユーザーレベルのスタック(引数をプッシュした場所)からスレッドの保護されたレベルのスタックに切り替えられることです。システムコールによって中断されたスレッドの現在のコンテキストは、実際にはこの保護されたスタックに保存されます。ISR内で、カーネルに入る直前に、この保護されたスタックは再びあなたが話しているカーネルスタック。カーネル内に入ると、スケジューラーの関数などのカーネル関数は最終的にカーネルスタックを使用します。その後、スレッドはスケジューラによって選出され、システムはISRに戻り、カーネルスタックから、新しく選出された(または、より優先度の高いスレッドがアクティブでない場合は前者の)スレッドの保護レベルスタックに戻ります。新しいスレッドコンテキスト。したがって、コンテキストはコードによってこのスタックから自動的に復元されます(基盤となるアーキテクチャによって異なります)。最後に、特別な命令により、スタックポインタや命令ポインタなどの最新のタッチレジスタが復元されます。ユーザーランドに戻る...

要約すると、スレッドには(通常)2つのスタックがあり、カーネル自体には1つのスタックがあります。カーネルスタックは、各カーネルが入る最後にワイプされます。2.6以降、カーネル自体が何らかの処理のためにスレッド化されるため、カーネルスレッドには、一般的なカーネルスタックの横に独自の保護レベルスタックがあることを指摘するのは興味深いことです。

いくつかのリソース:

  • 3.3.3 Linuxカーネルを理解するプロセス切り替えの実行、O'Reilly
  • 5.12.1 Intelのマニュアル3A(sysprogramming)の例外または割り込みハンドラの手順。章番号はエディションごとに異なる可能性があるため、「割り込みおよび例外処理ルーチンへの転送でのスタック使用量」を調べると、適切なものにたどり着くはずです。

この助けを願っています!

于 2012-09-28T13:33:27.433 に答える