シグナル(その3)
シグナルは送信元が送信先のプロセスにシグナルをセットし、そのプロセスを起床させ、送信先プロセスが実行権を得た後のユーザプロセスに戻る際に、そのシグナル処理をすると言うものでした。ここで考慮しなければならないのは、送信先プロセスが、TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE状態であった場合です。つまりシステムコールを呼び出して、IO待ちとか自らスリープしているケースです。この場合タスク状態はTASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE状態になり、そのリストはウエイトリストに繋がれます。この状態でシグナルを送信すると、その処理でプロセスをウエイトキューからランキューに繋ぎかえ、ウエイトキュー上のこのプロセスは取り除かれていました。
そうなると、ユーザプロセスとしてシグナルを処理した後の、このシステムコールの処理の取り扱いを考えねばなりません。
シグナルの配信は、カーネルモードからユーザモードに戻る時に処理されるものでした。すなわちシステムコール/ハード割り込み/例外割り込み等です。上記の処理をするためには、シグナル配信処理において、どの割り込み処理で呼ばれたか識別しなければなりません。
下記はシステムコールのエントリー処理です。#save orig_eaxとして、まずAXレジスタをプッシュし、その後全レジスタをプッシュしています。この時のAXレジスタはシステムコール番号です。
do_signal関数でget_signal_to_deliver関数はシグナルを補足しない、すなわち無視かシステムデフォルトの処理を行うもので、そうでない場合ユーザプロセスとして補足するシグナルを返してきます。そしてユーザプロセスで補足する処理はhandle_signal関数に委ねます。
従って以降のif ((long)regs->orig_ax >= 0)は、シグナルを補足していない時の処理となります。if ((long)regs->orig_ax >= 0)は上記のように、これはシステムコール中に呼ばれたと言う事です。そして再度axレジスタに呼ばれた時のaxレジスタ(システムコール番号)を設定し、ipレジスタを-2することで、再度同じシステムコールが実行されるようになっています。__NR_restart_syscallはスリープ時の処理で、いままでスリープした時間を考慮したスリープ時間で、再度スリープするらしいです。
・シグナルをユーザプロセスで補足している時
とこんな感じです。シグナルをユーザプロセスで補足している場合、その取り扱いには注意する必要がありそうです。なおERESTARTNOHAND/ERESTARTSYS/ERESTARTNOINTR/ERESTART_RESTARTBLOCKの値がどのタイミングで設定されるのか確認していません。たぶん各システムコール処理内で、TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLEに遷移する時にみずから設定しているのでは。と想像していますが・・・。
考察?
シグナルをユーザプロセスで補足している時で、ERESTART_RESTARTBLOCKがエラー(-EINTR)となっているのは、ユーザプロセスのシグナルハンドラで、その時のスリープ時間以上時間が経過しているケースでは、再度スリープする意味が無いからではと思います。なお、この考察はERESTART_RESTARTBLOCKがスリープ関連システムコールで設定されると言う前提ですが・・・。
そうなると、ユーザプロセスとしてシグナルを処理した後の、このシステムコールの処理の取り扱いを考えねばなりません。
シグナルの配信は、カーネルモードからユーザモードに戻る時に処理されるものでした。すなわちシステムコール/ハード割り込み/例外割り込み等です。上記の処理をするためには、シグナル配信処理において、どの割り込み処理で呼ばれたか識別しなければなりません。
下記はシステムコールのエントリー処理です。#save orig_eaxとして、まずAXレジスタをプッシュし、その後全レジスタをプッシュしています。この時のAXレジスタはシステムコール番号です。
ENTRY(system_call) RING0_INT_FRAME # can't unwind into user space anyway pushl %eax # save orig_eax CFI_ADJUST_CFA_OFFSET 4 SAVE_ALL GET_THREAD_INFO(%ebp) # system call tracing in operation / emulation testw $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) jnz syscall_trace_entry cmpl $(nr_syscalls), %eax jae syscall_badsys syscall_call: call *sys_call_table(,%eax,4)下記はハード割り込みエントリー処理です。まずpushl $~(vector)で-1をプッシュし、jmp common_interruptで全レジスターをプッシュしています。
ENTRY(irq_entries_start) RING0_INT_FRAME vector=0 .rept NR_IRQS ALIGN .if vector CFI_ADJUST_CFA_OFFSET -4 .endif 1: pushl $~(vector) CFI_ADJUST_CFA_OFFSET 4 jmp common_interrupt : : common_interrupt: SAVE_ALL TRACE_IRQS_OFF movl %esp,%eax call do_IRQ例外割り込みも同じです。負の値がプッシュされています。この最初にプッシュする値をregs->orig_axとして参照するようになっています。従ってこの値をチェックすることで、シグナル配信が、システムコール中に呼ばれたのかがチェックできるカラクリになっているようです。
do_signal関数でget_signal_to_deliver関数はシグナルを補足しない、すなわち無視かシステムデフォルトの処理を行うもので、そうでない場合ユーザプロセスとして補足するシグナルを返してきます。そしてユーザプロセスで補足する処理はhandle_signal関数に委ねます。
従って以降のif ((long)regs->orig_ax >= 0)は、シグナルを補足していない時の処理となります。if ((long)regs->orig_ax >= 0)は上記のように、これはシステムコール中に呼ばれたと言う事です。そして再度axレジスタに呼ばれた時のaxレジスタ(システムコール番号)を設定し、ipレジスタを-2することで、再度同じシステムコールが実行されるようになっています。__NR_restart_syscallはスリープ時の処理で、いままでスリープした時間を考慮したスリープ時間で、再度スリープするらしいです。
static void do_signal(struct pt_regs *regs) { : : signr = get_signal_to_deliver(&info, &ka, regs, NULL); if (signr > 0) { : if (handle_signal(signr, &info, &ka, oldset, regs) == 0) { current_thread_info()->status &= ~TS_RESTORE_SIGMASK; } return; } if ((long)regs->orig_ax >= 0) { /* Restart the system call - no handlers present */ switch (regs->ax) { case -ERESTARTNOHAND: case -ERESTARTSYS: case -ERESTARTNOINTR: regs->ax = regs->orig_ax; regs->ip -= 2; break; case -ERESTART_RESTARTBLOCK: regs->ax = __NR_restart_syscall; regs->ip -= 2; break; } } : }次はユーザプロセスがシグナルを補足した場合です。処理内容は補足しない場合と異なっています。なお、ここで設定するAX,IPレジスタ内容をユーザプロセスのスタックにシグナルフレームスタックとして複写し、(この時点ではカーネルスタック)、シグナルハンドラからretした時にこれをカーネルスタックに持ってきて、あたかもユーザプロセスから通常のシステムコールされたかのように動作するわけです。なお下記に2つの処理をまとめてみました。
static int handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka, sigset_t *oldset, struct pt_regs *regs) { if ((long)regs->orig_ax >= 0) { /* If so, check system call restarting.. */ switch (regs->ax) { case -ERESTART_RESTARTBLOCK: case -ERESTARTNOHAND: regs->ax = -EINTR; break; case -ERESTARTSYS: if (!(ka->sa.sa_flags & SA_RESTART)) { regs->ax = -EINTR; break; } /* fallthrough */ case -ERESTARTNOINTR: regs->ax = regs->orig_ax; regs->ip -= 2; break; } } : : return 0; }・シグナルをユーザプロセスで補足していない時(デフォルト/無視)
ERESTARTNOHAND | 再実行 |
ERESTARTSYS | 再実行 |
ERESTARTNOINTR | 再実行 |
ERESTART_RESTARTBLOCK | 再実行(待機時間を考慮) |
ERESTARTNOHAND | エラー(-EINTR) |
ERESTARTSYS | SA_RESTARTが設定時は再実行、そうでない時エラー(-EINTR) |
ERESTARTNOINTR | 再実行 |
ERESTART_RESTARTBLOCK | エラー(-EINTR) |
考察?
シグナルをユーザプロセスで補足している時で、ERESTART_RESTARTBLOCKがエラー(-EINTR)となっているのは、ユーザプロセスのシグナルハンドラで、その時のスリープ時間以上時間が経過しているケースでは、再度スリープする意味が無いからではと思います。なお、この考察はERESTART_RESTARTBLOCKがスリープ関連システムコールで設定されると言う前提ですが・・・。