前回に引き続きLinuxのシグナルの配信について調べた。
カーネルバージョン
v4.5.0
シグナルの配信
シグナルの配信はシステムコール(= "Interrupt came from user space"かな?)から、ユーザモードに戻るときに呼ばれるprepare_exit_to_usermode
で実行される。
ここで、プログラムカウンタ(ip)にシグナルハンドラのエントリポイントにセットしてrestore_regs_and_iret
を呼び出すことでハンドラが実行される。
/* Interrupt came from user space */
GLOBAL(retint_user)
mov %rsp,%rdi
call prepare_exit_to_usermode
TRACE_IRQS_IRETQ
SWAPGS
jmp restore_regs_and_iret
prepare_exit_to_usermode
を見ていくと、do_signal()
,handle_signal()
によって、対象のプロセスに保留されているシグナルのうち、ハンドラが設定されているものが呼び出される。
handle_signal()
ではまず、ユーザモードのスタックを作成する。このとき、シグナルハンドラからの戻り番地をsigreturn()
を呼び出すコードにセットする。
ユーザモードスタックにセットするのは次のrt_sigframe
構造体である。pretcode
が戻り番地になる。
struct rt_sigframe {
char __user *pretcode;
struct ucontext uc;
struct siginfo info;
/* fp state follows here */
};
x86_64では、pretcodeにvdsovdso_image_32.sym___kernel_sigreturn
がセットされるわけではなく、ka.sa.sa_restorer
がframe->pretcode
にセットされているようだ。
restorerはsigaction()
の引数のact
のメンバとしてアプリケーションからセットされる(man 2 sigaction 参照)が、ユーザが好きにセットするものではないのでlibcなどで決められている可能性があるかも。
/* Set up to return from userspace. If provided, use a stub
already in userspace. */
/* x86-64 should always use SA_RESTORER. */
if (ksig->ka.sa.sa_flags & SA_RESTORER) {
put_user_ex(ksig->ka.sa.sa_restorer, &frame->pretcode);
...
regs->sp = (unsigned long)frame;
また、シグナルハンドラのエントリポイントをipにセットすることで、ユーザモードに戻った時にシグナルハンドラが実行されるようにしている。
regs->ip = (unsigned long) ksig->ka.sa.sa_handler;
本来の呼び出し元のユーザモードのコンテキストは、同じくユーザモードスタック内に保存され,dxレジスタでポイントされる。
これらは呼び出されたsigreturn()
によって回復されるはず。
/* Create the ucontext. */
if (cpu_has_xsave)
put_user_ex(UC_FP_XSTATE, &frame->uc.uc_flags);
else
put_user_ex(0, &frame->uc.uc_flags);
put_user_ex(0, &frame->uc.uc_link);
save_altstack_ex(&frame->uc.uc_stack, regs->sp);
...
regs->dx = (unsigned long)&frame->uc;
まとめ
シグナルの配信は生成と違い、アーキテクチャ依存のコードとなっていて比較的複雑だった。
シグナルハンドラからの復帰をカーネルがフックするために、POSIXに規定されていないsigreturn()なるシステムコールを使っていることがわかった。