Linuxのシグナルの実装について調べた。
カーネルバージョン
v4.5.0
シグナルの仕組み
普通のシグナルと、リアルタイムシグナルの2種類がある。
普通のシグナルは同じ種類のシグナルを2回送っても1度しか配信されないことがあるが、リアルタイムシグナルはキューにつながって必ず生成した回数分届く(メモリが足りていれば)
シグナルは生成と配信という2段階を踏んで届く。
生成はシグナル発信側プロセスが受信側のプロセスのtask_struct->pending
に対して保留中のキューをつなげる操作のことで、
配信は受信側のプロセスのschedule()
の延長でdo_signalを呼び出す操作のことである。
条件分岐が複雑になるので、以下は普通のシグナルの場合だけをみる。
シグナル生成
__send_signal()
で生成される。
普通のシグナルがすでに保留されているかどうかはlegacy_queue()
で確認する。
引数のsignalsが現在保留されているシグナルのビットマップである。
static inline int legacy_queue(struct sigpending *signals, int sig)
{
return (sig < SIGRTMIN) && sigismember(&signals->signal, sig);
}
まだ保留されていない場合、次の処理でシグナルがキューに繋がれその情報をinfoに格納する。
SI_USER
はシグナルの送信者がユーザプロセスの場合、SI_KERNELはカーネルから。
キューに繋げられたら、sigaddset()で保留済みのビットを立てる。
q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,
override_rlimit);
if (q) {
list_add_tail(&q->list, &pending->list);
switch ((unsigned long) info) {
case (unsigned long) SEND_SIG_NOINFO:
q->info.si_signo = sig;
q->info.si_errno = 0;
q->info.si_code = SI_USER;
q->info.si_pid = task_tgid_nr_ns(current,
task_active_pid_ns(t));
q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
break;
case (unsigned long) SEND_SIG_PRIV:
q->info.si_signo = sig;
q->info.si_errno = 0;
q->info.si_code = SI_KERNEL;
q->info.si_pid = 0;
q->info.si_uid = 0;
break;
default:
copy_siginfo(&q->info, info);
if (from_ancestor_ns)
q->info.si_pid = 0;
break;
}
userns_fixup_signal_uid(&q->info, t);
} else if (!is_si_special(info)) {
...
out_set:
signalfd_notify(t, sig);
sigaddset(&pending->signal, sig);
complete_signal(sig, t, group);
ret:
trace_signal_generate(sig, info, t, group, result);
return ret;
シグナルの配信
システムコールからユーザモードに戻るとき、do_signal()
が呼び出されget_signal()
で保留中のシグナルハンドラを処理する。ハンドラが設定されている場合は、handle_signal()
で実行される。ユーザモードへ戻るときのレジスタ情報などを差し替えるのだろうと思う。詳細は次回記事参照。
void do_signal(struct pt_regs *regs)
{
struct ksignal ksig;
if (get_signal(&ksig)) {
/* Whee! Actually deliver the signal. */
handle_signal(&ksig, regs);
return;
}
...
プロセスごとのシグナルの状態と設定の確認
各プロセスごとのシグナルの保留状態、ブロックなどの設定は/proc/[pid]/status
から確認できる。
SigPndからSigCgtはシグナル番号のビットマップになっている。
例えばSigIgnは2進数にすると、100000000000001000000000000なので、13と27のシグナルがブロックされている。
juntaki@lab% cat /proc/1342/status
Name: emacs
State: S (sleeping)
Tgid: 1342
Ngid: 0
...
Threads: 2 #スレッド数
SigQ: 1/15922 #キューの長さと上限
SigPnd: 0000000000000000 保留中:task->pending.signal
ShdPnd: 0000000000000000 task->signal->shared_pending.signal
SigBlk: 0000000000000000 ブロックのマスク:task->blocked
SigIgn: 0000000004001000 ハンドラがSIG_IGN:task->sighand->action->sa.sa_handler
SigCgt: 00000001db816eff シグナルハンドラが指定されている
...
まとめ
シグナルは生成と配信の2段階で実現されている。
生成は比較的簡単な構造体の操作だけだが、配信はプロセス切り替えなどのアーキテクチャ依存の部分があるため、やや複雑だった。
次回は配信の箇所をもう少し読みたい。