web-dev-qa-db-ja.com

Linuxカーネルのschedule()+ switch_to()関数は実際にどのように機能しますか?

Linuxカーネルのスケジュールプロセスが実際にどのように機能するかを理解しようとしています。私の質問は、スケジューリングアルゴリズムについてではありません。関数schedule()switch_to()がどのように機能するかについてです。

説明しようと思います。私はそれを観た:

プロセスがタイムスライスを使い果たすと、フラグ_need_resched_がscheduler_tick()によって設定されます。カーネルはフラグをチェックし、フラグが設定されていることを確認し、schedule()(質問1に関連)を呼び出して新しいプロセスに切り替えます。このフラグは、別のプロセスを実行する価値があるため、スケジュールをできるだけ早く呼び出す必要があるというメッセージです。ユーザースペースに戻るか、割り込みから戻ると、_need_resched_フラグがチェックされます。設定されている場合、カーネルは続行する前にスケジューラーを呼び出します。

カーネルソース(linux-2.6.10-「Linuxカーネル開発、第2版」の本が基づいているバージョン)を調べると、一部のコードがschedule()関数を自発的に呼び出して、別のコードを与えることができることもわかりました。実行する権利を処理します。関数switch_to()が実際にコンテキストスイッチを実行する関数であることがわかりました。 switch_to()が実際に何をしているかを理解しようとして、アーキテクチャに依存するコードをいくつか調べました。

その振る舞いは私が答えを見つけることができなかったいくつかの質問を引き起こしました:

  1. switch_to()が終了すると、現在実行中のプロセスは何ですか? schedule()を呼び出したプロセス?または、次のプロセス、実行するために選択されたプロセスですか?

  2. schedule()が割り込みによって呼び出されると、実行するために選択されたプロセスは、割り込み処理が終了したときに実行を開始します(ある種のRTEの後)?またはその前に?

  3. 割り込みからschedule()関数を呼び出せない場合、フラグ-_need_resched_はいつ設定されますか?

  4. タイマー割り込みハンドラーが機能しているとき、どのスタックが使用されていますか?

はっきりさせることができるかどうかわかりません。できなかった場合は、いくつかの回答(または質問)の後でこれを実行できることを願っています。私はすでにそのプロセスを理解しようとしているいくつかの情報源を見ました。 「LinuxKernelDevelopment、sec ed」という本を持っていて、それも使っています。それが説明に役立つなら、私はMIPとH8300アーキテクチャについて少し知っています。

28
derf
  1. switch_to() を呼び出した後、カーネルスタックはnextで指定されたタスクのスタックに切り替えられます。アドレス空間の変更などは、例えば context_switch() で処理されます。
  2. schedule()は、割り込みからも含めて、アトミックコンテキストでは呼び出すことができません(チェックイン schedule_debug() を参照)。再スケジュールが必要な場合は、TIF_NEED_RESCHEDタスクフラグが設定されます。このフラグは、 割り込みリターンパス でチェックされます。
  3. 2を参照してください。
  4. デフォルトの8Kスタックでは、割り込みは現在実行されているカーネルスタックで処理されると思います。 4Kスタックが使用されている場合、別の割り込みスタックがあると思います(x86の魔法のおかげで自動的にロードされます)が、その点については完全にはわかりません。

もう少し詳しく説明すると、実際の例を次に示します。

  1. 割り込みが発生します。 CPUは、割り込みトランポリンルーチンに切り替わり、割り込み番号をスタックにプッシュしてから、 common_interrupt にジャンプします。
  2. common_interrupt呼び出し do_IRQ 、これは プリエンプション を無効にしてから IRQを処理します
  3. ある時点で、タスクを切り替えることが決定されます。これは、タイマー割り込みまたはモーニングコールが原因である可能性があります。いずれの場合も、 set_task_need_resched が呼び出され、TIF_NEED_RESCHEDタスクフラグが設定されます。
  4. 最終的に、CPUは元の割り込みでdo_IRQから戻り、 IRQ出口パスに進みます。 このIRQがカーネル内から呼び出された場合、 TIF_NEED_RESCHEDが設定されているかどうかを確認し 、設定されている場合は preempt_schedule_irq を呼び出します。 schedule()の実行中に割り込みます。
  5. IRQがユーザースペースから呼び出された場合は、戻る前に、まず 実行する必要があることがあるかどうかを確認します 。もしそうなら、 retint_careful に行き、保留中の再スケジュールをチェックし(そして必要に応じてschedule()を直接呼び出します)、保留中のシグナルをチェックし、重要なフラグが設定されなくなるまで、 _retint_check_ で別のラウンドに戻ります。
  6. 最後に、GSを 復元し、割り込みハンドラー から戻ります。

switch_to()について; switch_to()(x86-32上)の機能は次のとおりです。

  1. 後でこのタスクに戻るときのために、EIP(命令ポインター)とESP(スタックポインター)の現在の値を保存します。
  2. _current_task_の値を切り替えます。この時点で、currentは新しいタスクを指します。
  3. 新しいスタックに切り替えてから、切り替え先のタスクによって保存されたEIPをスタックにプッシュします。後で、このEIPをリターンアドレスとして使用して、リターンが実行されます。これは、以前にswitch_to()と呼ばれていた古いコードに戻る方法です。
  4. __ switch_to() を呼び出します。この時点で、currentは新しいタスクを指しており、新しいタスクのスタックにいますが、他のさまざまなCPU状態は更新されていません。 __switch_to()は、FPU、セグメント記述子、デバッグレジスタなどの状態の切り替えを処理します。
  5. __switch_to()から戻ると、switch_to()が手動でスタックにプッシュした戻りアドレスが返され、新しいのswitch_to()の前の場所に実行が戻されます。仕事。これで、切り替えられたタスクの実行が完全に再開されました。

x86-64は非常に似ていますが、ABIが異なるため、状態の保存/復元を少し増やす必要があります。

31
bdonlan