シグナル(その4)-システムコール再実行(sleep)
システムコールで待機中にシグナル配信処理が行われると、そのプロセスは起床され(TASK_RUN状態)、シグナル処理が動作する様なっていました。そして待機中だったシステムコールは、システムコールの返り値により、シグナル処理後再実行等の処理が行われるようになっていました。返り値とは、ERESTARTNOHAND/ERESTARTSYS/ERESTARTNOINTR/ERESTART_RESTARTBLOCK
です。シグナル(その3)時の理解では、この値は該当システムコール自身が設定するものと理解しましたが、ついでと言うことで調べるて見ました。
ERESTART_RESTARTBLOCKについては、スリープで待機した時間を考慮して、再実行するものでした。ということでシステムコールsleep内で、ERESTART_RESTARTBLOCKがどのように設定されるかを見てみようと思います。
スリープはnanosleepシステムコールが呼ばれます。大まかな処理はスリープする時間をhrtimer_sleeper(タイマース・リープリスト?)にリストして、プロセスをTASK_INTERRUPTIBLEにし、schedule関数をコールすることで、実行権を他のプロセスに譲渡するものです。スリープしたプロセスは割り込みタイマー毎に動的タイマーが起動され、そこでhrtimer_sleeperに繋がれたプロセスのスリープ時間がチェックされ、所定の時間がくれば、プロセスを起床すると言う流れです(たぶん)。
nanosleepシステムコールの主たる処理はhrtimer_nanosleepです。ここでスリープ時間を設定しているユーザパラメータを取得し、所定の構造体にセットしたのちdo_nanosleep関数をコールすることで実行権を譲渡します。このhrtimer_nanosleep関数で、ERESTARTNOHAND/ERESTART_RESTARTBLOCKの記述が見受けられます。どうやら上記の推測をあたっているようです。
まず、スリープが経過した時の返り値としてret=0としています。do_nanosleep関数の返り値が0で無いなら、設定したスリープ後に起床したということret=0がユーザプロセスの返り値となります。
do_nanosleep関数の返り値が0以外で、if (mode == HRTIMER_MODE_ABS)の時、-ERESTARTNOHANDを、そうでない時、-ERESTART_RESTARTBLOCKが返るようです。ここでの返値は対ユーザプロセスでなく、シグナル配信処理をするカーネルに対して参照されることになっていました。
HRTIMER_MODE_ABSはたぶん絶対時間として設定した場合と思われます。何分間待つというのでなく、何時何分に起床する。という。その場合、スリープ時間を考慮する必要はなさそうだとは、なんとなく想像できます。
そして、スリープ時間を考慮する必要がある場合、なにやらstruct restart_block *restartに、今までスリープした時間とか、どのタイマーリストに繋がれていた時の情報がセットされ、この情報でもって今までスリープした時間考慮してのスリープの再実行か可能だと想像できそうです。
すなわち、t->taskがNULLでなく、signal_pending関数でシグナルが送信されたかチェックしています。スリープ時間でもシグナルが送信されればプロセスは起床されるわけですから。そして、return t->task == NULLで所定の時間スリープした後の起床だと1を、そうでない起床だと0を返すことで、これをコールしたhrtimer_nanosleep関数で、返り値を0/ERESTARTNOHAND/ERESTART_RESTARTBLOCKか選定しているようです。
実はschedule関数をはさんでループする意味合いが分かりません。所定時間経過、シグナル以外にスリープ状態から起床する術があると言うことなのでしょうが・・・。
schedule関数の後で、hrtimer_cancel関数でタイマーをキャンセルして、mode = HRTIMER_MODE_ABSで絶対時間モードに設定しています。たぶん、通常時間のスリープ状態から、他のプロセスから絶対時間モードでスリープするよう変更されたケースのようにプログラムからは見て取れますが、そもそもそのような処理が可能かどうかも分かりません。とりあえず、シグナルのシステムコールの再実行の取り扱いの実装はこんな感じかなということです。
システムコールrestart_syscallは、スレッドインフォのrestart->fnをコールするだけです。これは上記のsleepシステム内のhrtimer_nanosleep関数で、シグナルで起床した場合に設定されるものでした。そこではhrtimer_nanosleep_restart関数が設定されています。
です。シグナル(その3)時の理解では、この値は該当システムコール自身が設定するものと理解しましたが、ついでと言うことで調べるて見ました。
ERESTART_RESTARTBLOCKについては、スリープで待機した時間を考慮して、再実行するものでした。ということでシステムコールsleep内で、ERESTART_RESTARTBLOCKがどのように設定されるかを見てみようと思います。
スリープはnanosleepシステムコールが呼ばれます。大まかな処理はスリープする時間をhrtimer_sleeper(タイマース・リープリスト?)にリストして、プロセスをTASK_INTERRUPTIBLEにし、schedule関数をコールすることで、実行権を他のプロセスに譲渡するものです。スリープしたプロセスは割り込みタイマー毎に動的タイマーが起動され、そこでhrtimer_sleeperに繋がれたプロセスのスリープ時間がチェックされ、所定の時間がくれば、プロセスを起床すると言う流れです(たぶん)。
nanosleepシステムコールの主たる処理はhrtimer_nanosleepです。ここでスリープ時間を設定しているユーザパラメータを取得し、所定の構造体にセットしたのちdo_nanosleep関数をコールすることで実行権を譲渡します。このhrtimer_nanosleep関数で、ERESTARTNOHAND/ERESTART_RESTARTBLOCKの記述が見受けられます。どうやら上記の推測をあたっているようです。
まず、スリープが経過した時の返り値としてret=0としています。do_nanosleep関数の返り値が0で無いなら、設定したスリープ後に起床したということret=0がユーザプロセスの返り値となります。
do_nanosleep関数の返り値が0以外で、if (mode == HRTIMER_MODE_ABS)の時、-ERESTARTNOHANDを、そうでない時、-ERESTART_RESTARTBLOCKが返るようです。ここでの返値は対ユーザプロセスでなく、シグナル配信処理をするカーネルに対して参照されることになっていました。
HRTIMER_MODE_ABSはたぶん絶対時間として設定した場合と思われます。何分間待つというのでなく、何時何分に起床する。という。その場合、スリープ時間を考慮する必要はなさそうだとは、なんとなく想像できます。
そして、スリープ時間を考慮する必要がある場合、なにやらstruct restart_block *restartに、今までスリープした時間とか、どのタイマーリストに繋がれていた時の情報がセットされ、この情報でもって今までスリープした時間考慮してのスリープの再実行か可能だと想像できそうです。
long hrtimer_nanosleep(struct timespec *rqtp, struct timespec __user *rmtp, const enum hrtimer_mode mode, const clockid_t clockid) { struct restart_block *restart; struct hrtimer_sleeper t; int ret = 0; hrtimer_init_on_stack(&t.timer, clockid, mode); t.timer.expires = timespec_to_ktime(*rqtp); if (do_nanosleep(&t, mode)) goto out; /* Absolute timers do not update the rmtp value and restart: */ if (mode == HRTIMER_MODE_ABS) { ret = -ERESTARTNOHAND; goto out; } if (rmtp) { ret = update_rmtp(&t.timer, rmtp); if (ret <= 0) goto out; } restart = ¤t_thread_info()->restart_block; restart->fn = hrtimer_nanosleep_restart; restart->nanosleep.index = t.timer.base->index; restart->nanosleep.rmtp = rmtp; restart->nanosleep.expires = t.timer.expires.tv64; ret = -ERESTART_RESTARTBLOCK; out: destroy_hrtimer_on_stack(&t.timer); return ret; }do_nanosleep関数のschedule関数をコールする事で、実行権を譲渡(スリープ状態)しまが、schedule関数をwhileでループしています。そしてこのループを抜ける条件が、t->task && !signal_pending(current)です。schedule関数からこのプロセスに実行権が移ってきたというのは、スリープ状態が終わったということです。そして動的タイマーから呼ばれる起床処理で、t->task = NULLとされています。それがループ脱出条件のt->task && !signal_pending(current)の意味合いです。
すなわち、t->taskがNULLでなく、signal_pending関数でシグナルが送信されたかチェックしています。スリープ時間でもシグナルが送信されればプロセスは起床されるわけですから。そして、return t->task == NULLで所定の時間スリープした後の起床だと1を、そうでない起床だと0を返すことで、これをコールしたhrtimer_nanosleep関数で、返り値を0/ERESTARTNOHAND/ERESTART_RESTARTBLOCKか選定しているようです。
static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mode) { hrtimer_init_sleeper(t, current); do { set_current_state(TASK_INTERRUPTIBLE); hrtimer_start(&t->timer, t->timer.expires, mode); if (!hrtimer_active(&t->timer)) t->task = NULL; if (likely(t->task)) schedule(); hrtimer_cancel(&t->timer); mode = HRTIMER_MODE_ABS; } while (t->task && !signal_pending(current)); __set_current_state(TASK_RUNNING); return t->task == NULL; }補足
実はschedule関数をはさんでループする意味合いが分かりません。所定時間経過、シグナル以外にスリープ状態から起床する術があると言うことなのでしょうが・・・。
schedule関数の後で、hrtimer_cancel関数でタイマーをキャンセルして、mode = HRTIMER_MODE_ABSで絶対時間モードに設定しています。たぶん、通常時間のスリープ状態から、他のプロセスから絶対時間モードでスリープするよう変更されたケースのようにプログラムからは見て取れますが、そもそもそのような処理が可能かどうかも分かりません。とりあえず、シグナルのシステムコールの再実行の取り扱いの実装はこんな感じかなということです。
システムコールrestart_syscallは、スレッドインフォのrestart->fnをコールするだけです。これは上記のsleepシステム内のhrtimer_nanosleep関数で、シグナルで起床した場合に設定されるものでした。そこではhrtimer_nanosleep_restart関数が設定されています。
SYSCALL_DEFINE0(restart_syscall) { struct restart_block *restart = ¤t_thread_info()->restart_block; return restart->fn(restart); }hrtimer_nanosleep_restart関数はhrtimer_nanosleep関数とその処理内容は変わりません。ただ前者はその引数をrestart_block *restartから、後者はユーザが設定する引数から、struct hrtimer_sleeper tを初期化するようです。
long __sched hrtimer_nanosleep_restart(struct restart_block *restart) { struct hrtimer_sleeper t; struct timespec __user *rmtp; int ret = 0; hrtimer_init_on_stack(&t.timer, restart->nanosleep.index, HRTIMER_MODE_ABS); t.timer.expires.tv64 = restart->nanosleep.expires; if (do_nanosleep(&t, HRTIMER_MODE_ABS)) goto out; rmtp = restart->nanosleep.rmtp; if (rmtp) { ret = update_rmtp(&t.timer, rmtp); if (ret <= 0) goto out; } /* The other values in restart are already filled in */ ret = -ERESTART_RESTARTBLOCK; out: destroy_hrtimer_on_stack(&t.timer); return ret; }