検索
特集

事象の待ち合わせ――プロセススケジューリング(その5)UNIX USER 2004年6月号「Linuxカーネル2.6解読室」より転載(2/2 ページ)

Linuxカーネル上で動作するプロセスの状態は、大きく分けて2つある。1つは実行可能状態で、もう1つは待機状態(休止状態)だ。ここでは待機状態のプロセスについて詳しく解説していく。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
前のページへ |       

起床処理

 待機状態プロセスを起床させる関数として、wake_up関数群があります。wake_up関数群は、プロセスをRUNキューに登録することと、プロセス状態をTASK_RUNNINGに変更することを行います。もし起床させたプロセスのほうが、現在実行中のプロセスより実行優先度が高かった場合、プロセススケジューラに対してプリエンプト要求も送ります。wake_up関数群には、表2のように微妙に動作が異なるさまざまなものがあります。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

パイプ処理で利用。パイプへの書き込みや読み出しにより、通信相手プロセスが実行可能状態になっても、現在の処理が終わるまで実行権を明け渡さない。パイプでつながったプロセス群は全体で1つの処理であるため、そのプロセス間でプリエンプトしてもオーバーヘッドが増えるだけであるため。

 WAITキューからプロセスを外すのは、実は起床したプロセス自身です(55)。通常wake_up処理側ではWAITキューの操作は行いません(WAITキュー操作まで行うwake_up処理も存在はします)。そのため、起床させられたプロセスは実行権を得るまでは、RUNキューとWAITキューの両方に登録された状態になります(図14)。

図14
図14 起床直後のプロセス(クリックで拡大します)

 図12の状態遷移図を見るとよく分かりますが、待機状態から直接実行権を得て実行状態になることはできず、必ず実行待ち状態を経由して、RUNキュー上で実行権が割り当てられるのを待ちます。

 実際に起床処理のコードを見てみましょう。wake_up関数の先で呼び出されるプロセス1つだけを起床させるtry_to_wake_up関数をのぞいてみることにします(リスト6)。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

 まず、これから起床するプロセスが属するRUNキューを選択し、ロックします(60)。プロセスはいずれかのRUNキュー(つまりCPU)に結び付けられており、前回動作した同じCPU上で実行されるようにします。

 次に起床するプロセスをRUNキューに登録します。プリエンプションを発生させない指定の場合は、__activate_task関数で単にRUNキューにつなぐだけです(63)。通常の場合は、activate_task関数を呼び出し(64)、実行優先度の再計算を行い(70)、RUNキューにつなぎます(73)。長く待機状態であったプロセスのほうが、より高い実行優先度を得られます。現在実行権を握っているカレントプロセスより実行優先度が高くなるようだと(65)、プロセススケジューラに対し、再スケジューリング要求(プリエンプト要求)を出します(66)。ほかのCPUのプロセススケジューラに対して要求する場合は、プロセッサ間割り込みを利用します。

 プロセスをRUNキューにつなぎ終わったら、プロセスを実行可能状態に遷移させます(67)。また、これらの処理の中で、task_struct構造体のactivatedメンバーに値を設定しています。activatedメンバーには表3のような意味があります。activatedメンバーの値はプロセススケジューラ(schedule関数)が、スケジューリングする際の参考値にし、この値が大きいほど、優先的にスケジューリングしようとします。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

 Linuxカーネル内のコードでは、プロセスを待機状態に遷移させるとき、sleep_on関数やsleep_on_interruptible関数を利用せず、その関数と同等のこと(WAITキュー操作とプロセススケジューラの呼び出し)を直接行っている個所があちこちにあります。これはなぜでしょうか? 実は微妙なタイミングが関係しています。先ほども述べたように、待機状態への遷移処理は、通常以下の手順を踏みます。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

 しかし、1と2の間で事象が起きてしまう可能性のある場合、運が悪いとこのプロセスは永久に起床させられることがなくなります。そのため、標準のsleep_on関数を利用する代わりに、次のような順序で処理を行います。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

 この手順によって、目的の事象が成立しているか調べた直後に事象が発生しても、そのプロセス自身はすでにWAITキューに登録されているため、その事象発生により、実行可能状態に戻されることになります。プロセススケジューラを呼び出したとき、自プロセス自身も実行対象の候補となります。

 事象の成立条件が単純なときは、wait_event/wait_event_interruptibleマクロ関数を利用しても、上記処理を簡単に記述できます。

 ところで、もう1つ実装上の疑問点を持たれた方もおられると思います。プロセスが待機状態になったとき、そのプロセス用のtask_struct構造体をWAITキューに直接登録しないのはなぜなのでしょうか? 実はこの構造には面白い特徴があり、プロセスを同時に複数のWAITキューに登録できます。複数の事象を同時に待ち合わせ、いずれかの事象が成立したら起床できます(図15)。この仕組みはselectシステムコールやpollシステムコールの実現に利用しています。

図15
図15 複数の事象待ち(クリックで拡大します)

そのほかの待機/起床処理関数

 待機/起床を行う関数の一種として、completionという仕組みも用意しています(表4)。この仕組みを利用すると、事象の発生回数とプロセスの起床回数とを揃えることができます。プロセスの待機処理前に事象が発生してしまっても、期待どおりに動作する作りになっています。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

子プロセスのスケジューリング

 forkシステムコールによって子プロセスが生成されたときは、この子プロセスを実行状態としてスケジューリング対象に加えます(wake_up_forked_process関数)。子プロセスの実行優先度は親プロセスから引き継ぎ、親プロセスと同じRUNキューの親プロセスの前に挿入し、プリエンプト要求を発生させます(set_need_resched関数)。これによって子プロセスは親プロセスより、少しだけ先に動作することになります。ほとんどの子プロセスはすぐにexecシステムコールを発行するため、この順序で動作させたほうが、プロセス空間のコピーオンライト処理の発生を抑制できるというメリットがあるためです。

 また子プロセスを生成したとき、親プロセスは実行割り当て時間の半分を子プロセスに譲るようになっています。この仕組みによって、特定のプロセスから大量にプロセスが生成された場合でも、そのことによるほかプロセスへ与える影響を最小限に抑え、スケジューリングの公平性を保つことができます。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

 プロセススケジューリングは、Linuxカーネルの動作を決定する最も基本的な機能であるため、少し詳しく説明させていただきました。それでも、すべては説明し切れておらず、疑問点が残った方もおられるかもしれません。しかし、プロセススケジューリングに関する重要な点はすべて押さえているつもりなので、その応用的なコードを見ても同様に理解できるものと考えます。何度も本連載を読み返しながら、Linuxカーネルのコードと格闘してください。


 本連載の連載予定はこちらで確認できます。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

前のページへ |       

Copyright(c)2010 SOFTBANK Creative Inc. All rights reserved.

ページトップに戻る