シグナル(その3)


シグナルは送信元が送信先のプロセスにシグナルをセットし、そのプロセスを起床させ、送信先プロセスが実行権を得た後のユーザプロセスに戻る際に、そのシグナル処理をすると言うものでした。ここで考慮しなければならないのは、送信先プロセスが、TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE状態であった場合です。つまりシステムコールを呼び出して、IO待ちとか自らスリープしているケースです。この場合タスク状態はTASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE状態になり、そのリストはウエイトリストに繋がれます。この状態でシグナルを送信すると、その処理でプロセスをウエイトキューからランキューに繋ぎかえ、ウエイトキュー上のこのプロセスは取り除かれていました。

そうなると、ユーザプロセスとしてシグナルを処理した後の、このシステムコールの処理の取り扱いを考えねばなりません。

シグナルの配信は、カーネルモードからユーザモードに戻る時に処理されるものでした。すなわちシステムコール/ハード割り込み/例外割り込み等です。上記の処理をするためには、シグナル配信処理において、どの割り込み処理で呼ばれたか識別しなければなりません。

下記はシステムコールのエントリー処理です。#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)
とこんな感じです。シグナルをユーザプロセスで補足している場合、その取り扱いには注意する必要がありそうです。なおERESTARTNOHAND/ERESTARTSYS/ERESTARTNOINTR/ERESTART_RESTARTBLOCKの値がどのタイミングで設定されるのか確認していません。たぶん各システムコール処理内で、TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLEに遷移する時にみずから設定しているのでは。と想像していますが・・・。

考察?
シグナルをユーザプロセスで補足している時で、ERESTART_RESTARTBLOCKがエラー(-EINTR)となっているのは、ユーザプロセスのシグナルハンドラで、その時のスリープ時間以上時間が経過しているケースでは、再度スリープする意味が無いからではと思います。なお、この考察はERESTART_RESTARTBLOCKがスリープ関連システムコールで設定されると言う前提ですが・・・。

最終更新 2011/01/20 16:50:40 - north
(2011/01/20 16:26:12 作成)


検索

アクセス数
3689250
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。