1, 设置新的action;
系统调用signal用于实现这个功能,当然也可以用sigaction系统调用,
SYSCALL_DEFINE2(signal, int, sig, __sighandler_t, handler)
{
struct k_sigaction new_sa, old_sa;
int ret;
new_sa.sa.sa_handler = handler;
new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK;
sigemptyset(&new_sa.sa.sa_mask);
ret = do_sigaction(sig, &new_sa, &old_sa);
return ret ? ret : (unsignedlong)old_sa.sa.sa_handler;
}
该系统调用分配一个新的action后,调用do_sigaction完成实际工作,最终返回旧的action的handler。
int do_sigaction(int sig,struct k_sigaction *act, struct k_sigaction *oact)
{
struct task_struct *t = current;
struct k_sigaction *k;
sigset_t mask;
……
k = &t->sighand->action[sig-1];
spin_lock_irq(¤t->sighand->siglock);
if (oact)
*oact = *k;/*保存旧的action*/
if (act) {
sigdelsetmask(&act->sa.sa_mask,
sigmask(SIGKILL) | sigmask(SIGSTOP));
*k = *act;/*设置新的action*/
/*对两种handler的特殊处理*/
if (sig_handler_ignored(sig_handler(t, sig), sig)) {
……
}
}
spin_unlock_irq(¤t->sighand->siglock);
return 0;
}
实现很简单,先保存旧的action,用于系统调用返回,然后设置新的action。
2, 发送信号
发送信号的系统调用有很多,最终都会调用__send_signal()函数。
staticint __send_signal(int sig,struct siginfo *info, struct task_struct *t,
int group, int from_ancestor_ns)
{
struct sigpending *pending;
struct sigqueue *q;
int override_rlimit;
……
/*找到需要挂起的队列*/
pending = group ? &t->signal->shared_pending : &t->pending;
……
/*分配队列项结构*/
q = __sigqueue_alloc(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 = 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;
}
} else if (!is_si_special(info)) {
if (sig >= SIGRTMIN && info->si_code != SI_USER)
return -EAGAIN;
}
out_set:
signalfd_notify(t, sig);/*唤醒action中的等待队列*/
sigaddset(&pending->signal, sig);/*设置信号ID位掩码,即上面所说的那个位图*/
complete_signal(sig, t, group);/*试着唤醒执行该信号的进程*/
return 0;
}
发送信号,即将该信号链接到制定进程的信号挂起队列上,最后试着唤醒执行该信号的进程。
3, 信号捕获与执行
说了这么一堆,但我们还不明白内核怎样确保进程的挂起信号得到处理呢?内核在允许进程恢复用户态下的执行之前,检查进程TIF_SIGPENDING标志的值。每当内核处理玩一个中断或异常时,就检查是否存在挂起信号,即我们可以这样理解,在每次由内核态切换到用户态前,内核都会发起信号队列的处理,具体的信号发起和体系结构有关,但最终为了处理阻塞的挂起信号,内核都会调用do_signal()函数,为说明程序执行框架,下面的代码省略了大部分无关的代码。
staticvoid do_signal(struct pt_regs *regs)
{
struct k_sigaction ka;
siginfo_t info;
int signr;
sigset_t *oldset;
……
/*获取挂起信号*/
signr = get_signal_to_deliver(&info, &ka, regs, NULL);
if (signr > 0) {
……
/*实际的信号处理*/
if (handle_signal(signr, &info, &ka, oldset, regs) == 0) {
current_thread_info()->status &= ~TS_RESTORE_SIGMASK;
}
return;
}
/*如果从系统调用返回*/
if (syscall_get_nr(current, regs) >= 0) {
/* Restart the system call - no handlers present */
switch (syscall_get_error(current, regs)) {
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
regs->ax = regs->orig_ax;
regs->ip -= 2;
break;
case -ERESTART_RESTARTBLOCK:
regs->ax = NR_restart_syscall;
regs->ip -= 2;
break;
}
}
……
}
该系统调用主要分为两部分执行,首先是冲挂起队列中查找信号,然后是执行信号处理函数。