Linuxのプロセス管理

Linux上の全てのプロセスはtask_struct構造体で管理されます。 また、Linuxカーネルは、常に全てのプロセスの全ての管理情報を 参照することができます。

Linuxプロセスの生成

forkシステムコールはプロセスのtask_struct構造体をコピーして新しい プロセスを生成します。
do_fork(フラグ, プロセスコンテキスト)
        空きtask_structを一つ確保(alloc_task_struct関数)
        プロセスIDを付ける(get_pid関数)
        task_structの各メンバの初期化
        ファイルディスクリプタテーブルのコピー(copy_files関数)
        カレントディレクトリ、umask等のコピー(copy_fs関数)
        シグナル情報のコピー(copy_sighand関数)
        親プロセスコンテキストのコピー(copy_thread関数)
        仮想空間をCopy-On-Writeで複製コピー(copy_mm関数)
        生成した子プロセスをRUNQに繋ぐ(wake_up_process関数)

Linuxプロセスの実行

生成されたプロセスはexecシステムイコールにより、新しいコマンドを実行することが できます。execシステムイコールは一度全ての仮想空間を解放し、その後 新しい空間を生成しマップします。
do_execve(ファイルパス, 引数・環境)
        ファイルのオープン(open_namei関数)
        exec後のユーザID/グループID計算、ファイルヘッダの読み込み(prepare_binprm関数)
        コマンド名、環境変数、起動引数を読み込む(copy_strings関数)
        各バイナリ種別毎のハンドラ呼び出し(search_binary_handler関数)

execシステムコールによって実行に移されたプロセスは次のテーブルにある状態を ある時点で取ることになります。
状態 説明
TASK_RUNNING 実行可能状態
TASK_INTERRUPTIBLE 待ち状態。シグナル受信可能
TASK_UNINTERRUPTIBLE 待ち状態。シグナル受信不可
TASK_ZOMBIE ゾンビ状態。exit後の状態
TASK_STOPPED サスペンド状態
CPU上で実行可能なプロセスはTASK_RUNNING状態になっています。 複数あるTASK_RUNNING状態のプロセスのうち最も高い プライオリティを持つタスクにCPUが与えられます。
TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLEは、共にある条件の 成立を待って実行を中断している状態です(ディスクへのI/O要求を 発行した後にI/Oが完了するのを待っている状態,TTY端末からの 入力を待っている状態など)。 この待ち状態に二つの状態が用意されているのは、待っている間に シグナルを送られたとき、この待ちを解除するか否かを制御するためです。 TASK_INTERRUPTIBLE状態で待ちに入っている場合は、強制的に 起床されます。(ディスクへのI/O完了待ちでは、I/O処理完了まで シグナルのハンドリングを遅らせるためにTASK_UNINTERRUPTIBLEで 待つようにしますが、起床する時が保証されないTTY端末I/O待ちの 場合は、TASK_INTERRUPTIBLEで待ちに入ります)
TASK_STOPPED状態は、サスペンドシグナル(SIGSTOPやSIGTTINなど〕 を送られて実行中断状態になった状態を示します。この状態のプロセスは スケジューリングの対象となりません。リジュームシグナル(SIGCONT) が送られると、TASK_RUNNING状態に戻され、スケージューリングの 対象となります。
TASK_ZOMBIE状態は、exitしてから消滅するまでの間のプロセス状態で す。プロセスは死んだ後も親プロセスにwaitを実行してもらうまでは、 TASK_ZOMBIE状態としてシステム内に存在し続けます。

Linuxプロセスの終了

最後に、プロセスの終了はdo_exit関数で行われます。明示的にexitシステムコールを 呼び出したとき以外にも、シグナルを受けて死ぬときなどにも呼び出されます。 do_exit関数ではtask_structを除く全ての資源の解放を行います。 do_exitはexit_notify関数で親プロセスにSIGCHLDを送出します。 SIGCHLDを受け取った親プロセスは、ZOMBIE状態になった子プロセスを 探し出しtask_structの解放を行います。

do_exit(終了コード)
{
        このプロセス用のタイマを止める(del_timer_sync関数)
        IPCセマフォの解放(sem_exit関数)
        仮想空間の解放(__exit_mm関数)
        ファイルのクローズと管理域の解放(__exit_files関数)
        カレントディレクトリ、umask情報の解放(__exit_fs関数)
        シグナルの破棄と管理領域の解放(__exit_sighand関数)
        プロセス状態をTASK_ZOMBIEに変更する
        親プロセスへの通知(exit_notify関数)
        CPUの放棄(schedule関数)
}

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

Linuxスケジューラ

Linuxスケジューラはプロセスとスレッドを全く区別せずに扱います。 実行可能なプロセス(スレッド)はRUNキューにリンク されてます。 スケジューラはこのRUNキューに継っているプロセスのうち 最も高い優先 度を持つプロセスを選び出しCPU(実行権)を与えます。 現在実行中のプロセスはcurrentというポインタによ り指されています。 何も実行するプロセスがなくなると、スケジューラはidleと呼ばれる 何もしないプロセスに実行権を渡します。

プロセスの切り替え

プロセスの切替えとは、現在走行中のプロセスのコンテキストを保存し、 次に走行するプロセスのコンテキストを CPU上にロードする作業です。 再び走行を開始するときは、先程メモリ上にセーブしたコンテキストを CPU上にロードしなおせば、中断地点から処理を再開することができます。
Linuxではswitch_to関数がその作業を担っています。 コンテキストセーブ域としては、プロセスのカーネルスタックと struct_task内にとられた領域(tss域)を利用しいます。

プロセスの同期

走行中のプロセスが待ちに入る場合、待ち対象毎に 用意されているwaitキューヘッドに自分自身を繋ぎCPUを放棄します。 (自分自身をRUNキューからはずし、スケジューラを呼び出す) sleep_on関数、 interruptible_sleep_on関数が用意されています。 この二つの関数の違いは、WAIT状態になったときのプロセスが シグナルにより起床するかしないかという点です。
このプロセスはイベントの発生により起床されます。 wake_up関数、wake_up_interruptible関数などによって 起床されたばかりのプロセスはRUNキュー継っていますが、 waitキューヘッドの方にも 継ったままになっています。 このプロセスは再度実行権が与えられた時に、 まず最初に自分自身を waitキューヘッドから外す処理を行います。

v2.2までは、wake_up関数は対象となるWAITキューヘッドで待ちに入っている プロセスを全てRUN状態にしていましたが、性能改善のためv2.4からは WAITキューヘッドで待ちに入っているプロセスのうち先頭のプロセスだけを RUN状態にすることができるようになりました。プロセスの属性にTASK_EXCLUSIVEを 持たせると、起床処理時に先頭のプロセスのみ起床するようになります。

プリエンプション処理

プロセスがwake_up_process関数などにより走行可能となったとき、 RUNキューにリンクされますが、RUNキューにリンクしただけでは、 そのプロセスのプライオリティが幾ら高くても CPUの実行権を与えられることはありません。 このプロセスが現在走行中のプロセスよりプライオリティが高い時、 スケジューラに対してCPUの明け渡し要求(プリエンプト要求)を 出さねばなりません(reschedule_idle関数)。 プリエンプト要求は、 カレントプロセスのtask_structのneed_reschedメンバに印を付けることで 実現しています。 プリエンプト要求を受けたスケジューラは、 Linuxカーネルの処理が一区切りついたところで再スケジューリングを行います (schedule関数)。 再スケジューリングを行うのは、以下のポイントです。 また、これはLinuxカーネルのコード実行中にはプリエンプションが 発生しないことを意味しています。 Linuxカーネル内走行中のプロセスは明示的にスケジューラを 呼び出さない限り、 他のプロセスにCPUを奪われることはありません。 これはLinuxカーネル内の資源排他を単純化することに役立っています。

スレッド

Linuxプロセスの生成で触れたように、 forkシステムコールはプロセスのtask_struct構造体を コピーして新しいプロセスを生成します。 一方、cloneによるスレッド生成の場合は、それら資源の コピーを全く行いません。 代わりに両方のコンテキストから全くおなじ資源が参照できるように共有します。 しかし、それ以外の点では まったくプロセスと同等です。
当然スケジューラからもプロセスとスレッドを全く区別せずに扱われます。 おなじ空間を共有するスレッド(同一のプロセス 内のスレッド)であっても、 マルチプロセッサシステムの場合、 別々のCPU上で同時実行 されることもありえます。