シグナル(その2)
シグナルのユーザプロセスの配信は、ユーザプロセスがカーネルモードからユーザモードに戻る際に行われます。すなわちシステムコールとかハード割り込み処理後です。(ハード割り込みで、カーネルパスで割り込み発生がした場合、シグナル配信の処理は行われません。)
カーネルモードからユーザモードに戻る時、do_notify_resume関数が呼ばれます。そこで戻ろうとするユーザプロセスのthread_info_flagsにTIF_SIGPENDINGがセットされているなら、 struct pt_regs *regsを引数にしてdo_signal関数をコールすることで行っています。引数のstruct pt_regs *regsは、カーネルスタックに積んであるユーザモード時復帰するレジスア群(プロセスコンテキスト)です。
ユーザがシグナルコールバック関数を定義していると、do_signal関数からretして、システムコール等のret処理からユーザプロセス戻ると、ユーザプロセスはシグナルコールバック関数が動作するようにしなければなりません。そしてこのコールバック関数が終了すると、カーネルモードに以降した所(システムコールの発行地点)に戻る必要があります。
実はこれは厄介な事です。実装的にはレジスター群を退避しているカーネルスタックの、戻りアドレスを、ユーザプロセスのシグナルコールバック関数のアドレスに設定するわけですが、問題はコールバック関数からretした時、割り込まれたユーザプロセスに所に戻らなければなりません。これが問題です。カーネルモードからユーザモードに戻る時、そのスタックは復帰されて無くなっているからです。
そのため、カーネルはカーネルモードのスタック上のコンテキストに掛かる内容を、ユーザプロセスのスタックに複写します。そしてシグナルコールバック関数からretすると、このユーザプロセスのスタックをカーネルモードのスタックに複写することで、シグナルにより割り込まれた箇所から動作するようになっています。なお、シグナルコールバック関数のretは、そのスタック処理のため、カーネル内部のシステムコールが呼ばれるようなカラクリを施しています。
do_signal関数の処理です。まずuser_mode関数で本関数がハード割り込み時で、それがユーザプロセス下で発生したかチェックしています。これはCSレジスターの特権レベルチェックすれば分かります。
oldsetにマスクするシグナルの情報を設定しています。このif文の意味するところは分かりません。そしてget_signal_to_deliver関数で配信するシグナルがあるかチェックしています。
get_signal_to_deliver関数でシグナルの有無をチェックし、あればそれをキューから取り除いてそのシグナル番号を返すわけですが、ここでは、プロセスが他のプロセスから監視されていたり、あるいはその処理がコアダンプとか、ユーザプロセスでシグナル処理をしていない時の処理が行われていています。
if (signr > 0)で配信するシグナルが有ると言う事です。上記のスタックに掛かる処理を行うのがhandle_signal関数となります。
とりあえず、そのスタック位置がframeにセットされます。ユーザプロセスは、ユーザモードに移行すると、このスタック内容で動作するわけです。このframe(ユーザスタック)とりあえずsetup_sigcontext関数で、カーネルスタックのコンテキストをframeにセットしています。あと__put_user関数で、色々と所定の値を設定していますが、ポイントとなる箇所は__put_user(restorer, &frame->pretcode)です。restorerはシグナルユーザコールバック関数のretアドレスです。条件によって、
restorer = VDSO32_SYMBOL(current->mm->context.vdso, sigreturn);
restorer = &frame->retcode;
restorer = ka->sa.sa_restorer;
の3タイプあるようですが・・・。まあ、内容的にはユーザスタック上のコンテキストをカーネルスタックに戻しているんじゃないでしょうか?
そして、以下の3行です。
regs->sp = (unsigned long)frame;
regs->ip = (unsigned long)ka->sa.sa_handler;
regs->ax = (unsigned long)sig;
regsはカーネルスタックの内容で、ユーザプロセスに復帰する際に参照されるレジスター群です。そのスタックを上のfrmeにipを、ka->sa.sa_handlerすわなち、シグナルコールバック関数、axにその引数となるシグナルを設定しています。これで、ユーザプロセスにretするとき、スタックをframeとして、ka->sa.sa_handlerから実行されるわけです。しかもそのシグナルコールバック関数終了時は、frame->pretcodeが実行されるというみたいです。
カーネルモードからユーザモードに戻る時、do_notify_resume関数が呼ばれます。そこで戻ろうとするユーザプロセスのthread_info_flagsにTIF_SIGPENDINGがセットされているなら、 struct pt_regs *regsを引数にしてdo_signal関数をコールすることで行っています。引数のstruct pt_regs *regsは、カーネルスタックに積んであるユーザモード時復帰するレジスア群(プロセスコンテキスト)です。
ユーザがシグナルコールバック関数を定義していると、do_signal関数からretして、システムコール等のret処理からユーザプロセス戻ると、ユーザプロセスはシグナルコールバック関数が動作するようにしなければなりません。そしてこのコールバック関数が終了すると、カーネルモードに以降した所(システムコールの発行地点)に戻る必要があります。
実はこれは厄介な事です。実装的にはレジスター群を退避しているカーネルスタックの、戻りアドレスを、ユーザプロセスのシグナルコールバック関数のアドレスに設定するわけですが、問題はコールバック関数からretした時、割り込まれたユーザプロセスに所に戻らなければなりません。これが問題です。カーネルモードからユーザモードに戻る時、そのスタックは復帰されて無くなっているからです。
そのため、カーネルはカーネルモードのスタック上のコンテキストに掛かる内容を、ユーザプロセスのスタックに複写します。そしてシグナルコールバック関数からretすると、このユーザプロセスのスタックをカーネルモードのスタックに複写することで、シグナルにより割り込まれた箇所から動作するようになっています。なお、シグナルコールバック関数のretは、そのスタック処理のため、カーネル内部のシステムコールが呼ばれるようなカラクリを施しています。
do_signal関数の処理です。まずuser_mode関数で本関数がハード割り込み時で、それがユーザプロセス下で発生したかチェックしています。これはCSレジスターの特権レベルチェックすれば分かります。
oldsetにマスクするシグナルの情報を設定しています。このif文の意味するところは分かりません。そしてget_signal_to_deliver関数で配信するシグナルがあるかチェックしています。
get_signal_to_deliver関数でシグナルの有無をチェックし、あればそれをキューから取り除いてそのシグナル番号を返すわけですが、ここでは、プロセスが他のプロセスから監視されていたり、あるいはその処理がコアダンプとか、ユーザプロセスでシグナル処理をしていない時の処理が行われていています。
if (signr > 0)で配信するシグナルが有ると言う事です。上記のスタックに掛かる処理を行うのがhandle_signal関数となります。
static void do_signal(struct pt_regs *regs) { struct k_sigaction ka; siginfo_t info; int signr; sigset_t *oldset; if (!user_mode(regs)) return; if (current_thread_info()->status & TS_RESTORE_SIGMASK) oldset = ¤t->saved_sigmask; else oldset = ¤t->blocked; signr = get_signal_to_deliver(&info, &ka, regs, NULL); if (signr > 0) { if (current->thread.debugreg7) set_debugreg(current->thread.debugreg7, 7); if (handle_signal(signr, &info, &ka, oldset, regs) == 0) { current_thread_info()->status &= ~TS_RESTORE_SIGMASK; } return; } : : }handle_signal関数では、まずシステムコールでウエイト下で、シグナルによりユーザプロセスのコールバック関数に動作が移った場合、その後このシステムコールを再実行するかどうかのチェックらしいです。(よく分かりません。)で、シグナル処理のメインがsetup_rt_frame/setup_frame関数となります。ここでスタック処理を行います。
static int handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka, sigset_t *oldset, struct pt_regs *regs) { int ret; if ((long)regs->orig_ax >= 0) { 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; } case -ERESTARTNOINTR: regs->ax = regs->orig_ax; regs->ip -= 2; break; } } : if (ka->sa.sa_flags & SA_SIGINFO) ret = setup_rt_frame(sig, ka, info, oldset, regs); else ret = setup_frame(sig, ka, oldset, regs); if (ret) return ret; : : }setup_frame関数です。get_sigframe関数でユーザプロセスのスタックをシグナル処理のため拡張します。regs->spがユーザスタック位置です。なお、シグナルフレーム分拡張したスタック位置が、設定されているサイズより大きいと、シグナルスタックとして新規の別スタックで動作するようです。
とりあえず、そのスタック位置がframeにセットされます。ユーザプロセスは、ユーザモードに移行すると、このスタック内容で動作するわけです。このframe(ユーザスタック)とりあえずsetup_sigcontext関数で、カーネルスタックのコンテキストをframeにセットしています。あと__put_user関数で、色々と所定の値を設定していますが、ポイントとなる箇所は__put_user(restorer, &frame->pretcode)です。restorerはシグナルユーザコールバック関数のretアドレスです。条件によって、
restorer = VDSO32_SYMBOL(current->mm->context.vdso, sigreturn);
restorer = &frame->retcode;
restorer = ka->sa.sa_restorer;
の3タイプあるようですが・・・。まあ、内容的にはユーザスタック上のコンテキストをカーネルスタックに戻しているんじゃないでしょうか?
そして、以下の3行です。
regs->sp = (unsigned long)frame;
regs->ip = (unsigned long)ka->sa.sa_handler;
regs->ax = (unsigned long)sig;
regsはカーネルスタックの内容で、ユーザプロセスに復帰する際に参照されるレジスター群です。そのスタックを上のfrmeにipを、ka->sa.sa_handlerすわなち、シグナルコールバック関数、axにその引数となるシグナルを設定しています。これで、ユーザプロセスにretするとき、スタックをframeとして、ka->sa.sa_handlerから実行されるわけです。しかもそのシグナルコールバック関数終了時は、frame->pretcodeが実行されるというみたいです。
static int setup_frame(int sig, struct k_sigaction *ka, sigset_t *set, struct pt_regs *regs) { struct sigframe __user *frame; void __user *restorer; int err = 0; int usig; frame = get_sigframe(ka, regs, sizeof(*frame)); if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) goto give_sigsegv; usig = current_thread_info()->exec_domain && current_thread_info()->exec_domain->signal_invmap && sig < 32 ? current_thread_info()->exec_domain->signal_invmap[sig] : sig; err = __put_user(usig, &frame->sig); if (err) goto give_sigsegv; err = setup_sigcontext(&frame->sc, &frame->fpstate, regs, set->sig[0]); if (err) goto give_sigsegv; if (_NSIG_WORDS > 1) { err = __copy_to_user(&frame->extramask, &set->sig[1], sizeof(frame->extramask)); if (err) goto give_sigsegv; } if (current->mm->context.vdso) restorer = VDSO32_SYMBOL(current->mm->context.vdso, sigreturn); else restorer = &frame->retcode; if (ka->sa.sa_flags & SA_RESTORER) restorer = ka->sa.sa_restorer; err |= __put_user(restorer, &frame->pretcode); err |= __put_user(0xb858, (short __user *)(frame->retcode+0)); err |= __put_user(__NR_sigreturn, (int __user *)(frame->retcode+2)); err |= __put_user(0x80cd, (short __user *)(frame->retcode+6)); regs->sp = (unsigned long)frame; regs->ip = (unsigned long)ka->sa.sa_handler; regs->ax = (unsigned long)sig; regs->dx = 0; regs->cx = 0; regs->ds = __USER_DS; regs->es = __USER_DS; regs->ss = __USER_DS; regs->cs = __USER_CS; return 0; give_sigsegv: force_sigsegv(sig, current); return -EFAULT; }