シグナルのブロック/無視の覚書き
少し調べていたのでメモ。基本的にはカーネル内の話。コードはv4.9.6
シグナルに関しては言葉がややこしいので、以下のようにこの記事では使う。
シグナルの登録 シグナルが生成され対象のタスクのpendingリストに繋がった状態。
シグナルの受信 シグナルが登録されたことがタスクに通知された状態。(TIF_SIGPENDINGがたっている状態)
シグナルの配信 受信したシグナルに応じた処理を行う。
Linuxのシグナルには「ブロックする」と「無視する」の二つがある。 違いは以下の通り
- ブロック
シグナルは対象のタスクに登録されるが、受信されない。
struct task_struct
のblocked
、real_blocked
で指定されたシグナルがブロックされる。
struct task_struct { #ifdef CONFIG_THREAD_INFO_IN_TASK /* * For reasons of header soup (see current_thread_info()), this * must be the first element of task_struct. */ ... sigset_t blocked, real_blocked; ...
- 無視
そもそもシグナルが登録されない。
シグナルを送る__send_signal()
では以下のようにブロックと無視が実現されている。
static int __send_signal(int sig, struct siginfo *info, struct task_struct *t, int group, int from_ancestor_ns) { struct sigpending *pending; ... result = TRACE_SIGNAL_IGNORED; if (!prepare_signal(sig, t, ★ from_ancestor_ns || (info == SEND_SIG_FORCED))) goto ret; pending = group ? &t->signal->shared_pending : &t->pending; ... 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) { ... 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; }
そもそもシグナルを登録するかどうかがprepare_signal()
で判断される。
ブロックするものは登録される。無視されるものは登録すらされない。
static bool prepare_signal(int sig, struct task_struct *p, bool force) { struct signal_struct *signal = p->signal; struct task_struct *t; sigset_t flush; ... return !sig_ignored(p, sig, force); ★ } static int sig_ignored(struct task_struct *t, int sig, bool force) { /* * Blocked signals are never ignored, since the * signal handler may change by the time it is * unblocked. */ // コメントに書かれている通り、blockedに登録されていてもシグナルは無視されない // ブロック状態のシグナルでもprepare_signal()が真になり、登録される。 if (sigismember(&t->blocked, sig) || sigismember(&t->real_blocked, sig)) return 0; if (!sig_task_ignored(t, sig, force)) ★ return 0; /* * Tracers may want to know about even ignored signals. */ // ptraceされていない場合はシグナルは無視される。 return !t->ptrace; } static int sig_task_ignored(struct task_struct *t, int sig, bool force) { void __user *handler; handler = sig_handler(t, sig); if (unlikely(t->signal->flags & SIGNAL_UNKILLABLE) && handler == SIG_DFL && !force) return 1; return sig_handler_ignored(handler, sig); ★ } static int sig_handler_ignored(void __user *handler, int sig) { /* Is it explicitly or implicitly ignored? */ // handlerがSIG_IGNになっている場合は無視 // handlerがSIG_DFLの場合は、sig_kernel_ignoreが真なら無視(↓参照) return handler == SIG_IGN || (handler == SIG_DFL && sig_kernel_ignore(sig)); } #define sig_kernel_ignore(sig) siginmask(sig, SIG_KERNEL_IGNORE_MASK) #define SIG_KERNEL_IGNORE_MASK (\ rt_sigmask(SIGCONT) | rt_sigmask(SIGCHLD) | \ rt_sigmask(SIGWINCH) | rt_sigmask(SIGURG) )
parepare_signal()
が真の場合、シグナルが生成され登録される。
最後にタスクを起床し、受信させるかどうかの判断はcomplete_signal()
で行われる。
static void complete_signal(int sig, struct task_struct *p, int group) { struct signal_struct *signal = p->signal; struct task_struct *t; /* * Now find a thread we can wake up to take the signal off the queue. * * If the main thread wants the signal, it gets first crack. * Probably the least surprising to the average bear. */ if (wants_signal(sig, p)) ★ t = p; else if (!group || thread_group_empty(p)) /* * There is just one thread and it does not need to be woken. * It will dequeue unblocked signals before it runs again. */ // wants_signal()が成立せず、プロセスグループ内にスレッドが1個なら何もせず復帰する。 return; ... /* * The signal is already in the shared-pending queue. * Tell the chosen thread to wake up and dequeue it. */ signal_wake_up(t, sig == SIGKILL); // ここで起床する。 return; } static inline int wants_signal(int sig, struct task_struct *p) { // blockedになっていたら0が返る。 if (sigismember(&p->blocked, sig)) return 0; if (p->flags & PF_EXITING) return 0; if (sig == SIGKILL) return 1; if (task_is_stopped_or_traced(p)) return 0; return task_curr(p) || !signal_pending(p); }
無視されず、ブロックされないシグナルの場合、対象のタスクはsignal_wake_up()
で起床させられる。
カーネルスレッドでは親のkthreaddが全シグナルのハンドラをSIG_IGN
にしているため、
通常は全シグナルが無視される。
int kthreadd(void *unused) { struct task_struct *tsk = current; /* Setup a clean context for our children to inherit. */ set_task_comm(tsk, "kthreadd"); ignore_signals(tsk); ★ ... void ignore_signals(struct task_struct *t) { int i; for (i = 0; i < _NSIG; ++i) t->sighand->action[i].sa.sa_handler = SIG_IGN; flush_signals(t); }
sigfillset()
などの関数を使うと、blockedを一括してセットすることが可能。
static inline void sigfillset(sigset_t *set) { switch (_NSIG_WORDS) { default: memset(set, -1, sizeof(sigset_t)); break; case 2: set->sig[1] = -1; case 1: set->sig[0] = -1; break; } }