1. 概述
应用程序注册信号,信号事件发生后,内核将信号置为pending状态,在中断返回或者系统调用返回时,查看pending的信号,内核在应用程序的栈上构建一个信号处理栈帧,然后通过中断返回或者系统调用返回到用户态,执行信号处理函数。执行信号处理函数之后,再次通过sigreturn系统调用返回到内核,在内核中再次返回到应用程序被中断打断的地方或者系统调用返回的地方接着运行。
如果应用程序没有注册对应的信号处理函数,那么信号发生后,内核按照内核默认的信号处理方式处理该信号,比如访问非法内存地址,发生SIGSEGV,内核将进程终止。
2. 基本数据结构
- task_strcut
struct task_struct {
...
struct signal_struct *signal; // 指向线程组的struct signal结构体
struct sighand_struct *sighand; // 描述的action,即信号发生后,如何处理
sigset_t blocked, real_blocked; // 该线程阻塞的信号,信号被阻塞,但是可以pending
// 当取消阻塞后,就可以执行信号处理
sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
struct sigpending pending; // 该线程的pending信号
unsigned long sas_ss_sp; // 用户执行信号执行栈、栈大小、flag
size_t sas_ss_size; // 可以通过系统调用sigaltstack指定
unsigned sas_ss_flags;
...
};
- signal_struct
struct signal_struct {
...
/* shared signal handling: */
struct sigpending shared_pending; // 线程组的pending信号
...
}
- sighand_struct
struct sighand_struct {
atomic_t count;
struct k_sigaction action[_NSIG]; // 描述信号的action,即信号发生时,如何处理该信号
spinlock_t siglock;
wait_queue_head_t signalfd_wqh;
};
- sigpending
struct sigpending {
struct list_head list; // sigqueue的链表头,用来链接处于pending状态的信号
sigset_t signal; // 处于Pending的sig号集合
};
- sigset_t
#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
typedef struct {
unsigned long sig[_NSIG_WORDS]; // 是一个位掩码,对应的信号会置位/清零
} sigset_t;
这些数据结构组织在一起,如下图。每个线程有一个自己私有的信号pending链表,链表上是发送给该线程,需要该线程自己去处理的信号。struct sigpending
的成员head_list
是pending信号sigqueue的链表头,成员signal
是这些pending信号的信号编号掩码。
另外,属于同一个进程的线程组共享一个信号pending链表,这个链表上的信号没有绑定特定的线程,该线程租中的任意线程都可以处理。
线程组还共享sighand数据结构,里面有64个信号的action。每个action用来描述如何处理该信号,比如忽略、默认方式处理、或者执行用户注册的信号handler。
3. 注册信号
用户态的应用程序通过系统调用函数signal
或者函数sigaction
注册一个信号服务,当进程或者线程接收到信号后,调用注册的信号服务函数。
虽然现在内核源码中
kernel/signal.c
中有定义signal
系统调用,但是libc库中使用系统调用sigaction
实现应用程序中使用的signal
函数。
3.1 内核中sigaction系统调用
内核kernel/signal.c
中定义了系统调用sigaction
函数。
该系统调用的功能是为信号sig注册新的信号action,同时返回旧的信号action。如果输入参数act为空,则不注册新的信号action,如果输出参数oact为空,则不返回老的信号action。
sigaction
系统调用函数代码主要流程:
- 如果输入参数
act
不为空,检查act
的合法性,并读取act
的各个成员,然后赋值个局部变量new_ka
- 调用
do_sigaction
做底层的工作 - 如果输出参数
oact
不为空,检查oact
的合法性,并将旧的信号action通过oact
返回
#ifdef CONFIG_COMPAT_OLD_SIGACTION
COMPAT_SYSCALL_DEFINE3(sigaction, int, sig,
const struct compat_old_sigaction __user *, act,
struct compat_old_sigaction __user *, oact)
{
struct k_sigaction new_ka, old_ka;
int ret;
compat_old_sigset_t mask;
compat_uptr_t handler, restorer;
if (act) {
if (!access_ok(VERIFY_READ, act, sizeof(*act)) ||
__get_user(handler, &act->sa_handler) ||
__get_user(restorer, &act->sa_restorer) ||
__get_user(new_ka.sa.sa_flags, &act->sa_flags) ||
__get_user(mask, &act->sa_mask))
return -EFAULT;
#ifdef __ARCH_HAS_KA_RESTORER
new_ka.ka_restorer = NULL;
#endif
new_ka.sa.sa_handler = compat_ptr(handler);
new_ka.sa.sa_restorer = compat_ptr(restorer);
siginitset(&new_ka.sa.sa_mask, mask);
}
ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL);
if (!ret && oact) {
if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)) ||
__put_user(ptr_to_compat(old_ka.sa.sa_handler),
&oact->sa_handler) ||
__put_user(ptr_to_compat(old_ka.sa.sa_restorer),
&oact->sa_restorer) ||
__put_user(old_ka.sa.sa_flags, &oact->sa_flags) ||
__put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask))
return -EFAULT;
}
return ret;
}
#endif
do_sigaction
函数代码流程:
- 检查sig是否是合法值。这里除了检查sig数值的合法性,也要求用户不能注册
SIGKILL
和SIGSTOP
信号action,这两种信号由内核处理,应用程序不能拦截。 - 将信号sig的action注册到sighand数组中,并返回旧的action
- 如果新注册的信号为可忽略信号,那么丢弃线程组共享的pending链表和线程组内各线程私有的pending链表中已经pending的sigqueue.
int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
{
struct task_struct *p = current, *t;
struct k_sigaction *k;
sigset_t mask;
// 检查sig是否是合法值:
// 1. 1 <= sig <= _NSIG
// 2. sig不是Kernel特有的:SIGKILL, SIGSTOP,
// 内核不允许注册SIGKILL, SIGSTOP的信号处理函数,这两种信号由内核自己处理
if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig)))
return -EINVAL;
k = &p->sighand->action[sig-1];
spin_lock_irq(&p->sighand->siglock);
// 返回旧的信号action
if (oact)
*oact = *k;
sigaction_compat_abi(act, oact);
if (act) {
// 确保act->sa.sa_mask中没有SIGKILL和SIGSTOP
sigdelsetmask(&act->sa.sa_mask,
sigmask(SIGKILL) | sigmask(SIGSTOP));
*k = *act; // 赋值新的信号action
/*
* POSIX 3.3.1.3:
* "Setting a signal action to SIG_IGN for a signal that is
* pending shall cause the pending signal to be discarded,
* whether or not it is blocked."
*
* "Setting a signal action to SIG_DFL for a signal that is
* pending and whose default action is to ignore the signal
* (for example, SIGCHLD), shall cause the pending signal to
* be discarded, whether or not it is blocked"
*/
// 如果当前进程忽略该信号:1. handler == IGNR
// 或者
// 2. handler == DFLT && (sig为SIGCONT | SIGCHLD | SIGWINCH |SIGURG之一)
// 那么,丢弃已经pending的信号
if (sig_handler_ignored(sig_handler(p, sig), sig)) {
sigemptyset(&mask);
sigaddset(&mask, sig);
// 清除共享信号中该sig的pending信号
flush_sigqueue_mask(&mask, &p->signal->shared_pending);
// 清除线程组中其他新线程该sig的pending信号
for_each_thread(p, t)
flush_sigqueue_mask(&mask, &t->pending);
}
}
spin_unlock_irq(&p->sighand->siglock);
return 0;
}
注:创建进程时,会将该进程的所有线程添加到task_struct中signal->thread_head链表中
4. send_signal 发送信号
发送信号,即在进程共享信号pending链表或者线程信号pending链表中添加一个信号sigqueue
,并置信号接收者TIF_SIGPENDING
位。如果信号接收者在此时没有在运行,那么直接唤醒;如果正在运行,有可能在用户态运行,那么kick_process
给信号接收者发送一个IPI,让其进入一次kernel。最终,信号处理是在线程由内核返回用户态的时候,检查线程的TIF_SIGPENDING
是否有置位,如果有,就执行信号的服务函数。
static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
enum