少し調べていたのでメモ。基本的にはカーネル内の話。コードは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;
}
}