プロセスの同期

走行中のプロセスが待ちに入る場合、待ち対象のオブジェクト毎に用意されているwaitキューヘッドに自分自身を繋ぎCPUを放棄する。(自分自身をRUNキューからはずし、スケジューラを呼び出す) sleep_on関数、interruptible_sleep_on関数が用意されている。この二つの関数の違いは、WAIT状態になったときのプロセスがシグナルにより起床するかしないかという点である。

img6.gif
  sleep_on(WAITキューヘッド) (または、interruptible_sleep_on(WAITキューヘッド))
        スタック上にwait_queueを用意
        カレントプロセスの状態をTASK_UNINTERRUPTIBLEに変更
            (interruptible_sleep_onの時はTASK_INTERRUPTIBLE)
        wait_queueにカレントタスクを登録。
        WAITキューヘッドにwait_queueをリンク(add_wait_queue関数)
        スケジューラを呼び出してCPUを放棄する(schedule関数)
        WAITキューヘッドからwait_queueを外す(remove_wait_queue関数)

このプロセスはイベントの発生により起床される。(wake_up関数、wake_up_interruptible関数など)起床されたばかりのプロセスはRUNキュー継っているが、waitキューヘッドの方にも継ったままになっている。このプロセスは再度実行権が与えられた時に、まず最初に自分自身をwaitキューヘッドから外す処理を行う。

この方式のメリットは、割り込みハンドラなどの延長でプロセス起床を行う場合、waitキューヘッドの操作を行わないで良く、排他処理を単純化できるところにある。

img7.gif
   __wake_up(WAITキューヘッド、モード) 
        while(WAITキューヘッドで待っている全てのプロセスに対し) {
             モードに指定されている属性(TASK_INTERRUPTIBLE等)の
                 プロセスなら起床(__wake_up_process関数)。
             一つだけ起床する指定(TASK_EXCLUSIVE)なら、break;
        }

   __wake_up_process(プロセス)
        プロセスの状態をTASK_RUNNINGに変更
        if(このプロセスが、まだRUNキューに継っていないなら)
              RUNキューにつなぐ(add_to_runqueue関数)
              再スケジューリング要求(reschedule_idle関数)
        }

v2.2までは、wake_up関数は対象となるWAITキューヘッドで待ちに入っているプロセスを全てRUN状態にしていたが、性能改善のためv2.4からはWAITキューヘッドで待ちに入っているプロセスのうち先頭のプロセスだけをRUN状態にすることができるようになった。後で説明するセマフォの他、負荷の集中する箇所で利用されている。プロセスの属性にTASK_EXCLUSIVEを持たせると、起床処理時に先頭のプロセスのみ起床するようになる。

wake_up()関数にはラッパがかぶせられており、使いやすさとv2.2までの関数との互換性が考慮されている。

  • wake_up(WAITキューヘッド)
    • TASK_UNINTERRUPTIBLE、TASK_INTERRUPTIBLE両方で待ちに入っている プロセスを起こす。ただし起こすプロセスは先頭の一つだけ (TASK_EXCLUSIVE)とする。(TASK_EXCLUSIVE属性で待ちに入っている プロセスを起こしたら、あとのプロセスは起こさない)
  • wake_up_all(WAITキューヘッド)
    • TASK_UNINTERRUPTIBLE、TASK_INTERRUPTIBLE両方で待ちに入っている プロセスを全て起こす。(TASK_EXCLUSIVE属性は無視する)
  • wake_up_interruptible(WAITキューヘッド)
    • TASK_INTERRUPTIBLE両方で待ちに入っている プロセスを起こす。ただし起こすプロセスは先頭の一つだけ (TASK_EXCLUSIVE)とする。(TASK_EXCLUSIVE属性で待ちに入っている プロセスを起こしたら、あとのプロセスは起こさない)
  • wake_up_interruptible_all(WAITキューヘッド)
    • TASK_INTERRUPTIBLE両方で待ちに入っているプロセスを全て起こす。

補足説明 1

通常待ちにはいる時は、sleep_on関数、またはinterruptible_sleep_on関数を呼び出すが、linuxカーネル中には、sleep_on関数のコードを展開した形で記述してある箇所が数多くある。自らwaitキューを操作し、自プロセスのステータスを変更し、スケジューラを呼び出す処理を行っている。

補足説明 2

伝統的なUNIXでは、sleep中にシグナルで強制的に起こされると、デフォルト状態ではシステムコール出口までロングジャンプして一気にシステムコールをEINTRで終了させているが、Linuxではごく普通に起き上がってくるだけである。ロングジャンプ機能はない。


(NIS)HirokazuTakahashi
2000年12月09日 (土) 23時55分06秒 JST
1