信号的概念大家不陌生。经常使用的kill命令,可以先运行的进程发送信号,运行进程在收到信号后,做出相应的处理。在用户态程序中关于信号的编程,主要有如下的特点
1、程序可以给不同的信号安装特定的handler程序,这个handler有一定的格式,由用户程序提供
2、应用程序可以配置,阻塞一些信号,使得程序可以不响应这些信号
3、信号一般有默认的handler处理流程,应用程序如果不主动设置handler,则使用系统默认的handler
4、有一些信号,用户不能设置handler也不能阻塞
一般的用户态信号变成模式如下
我们不去分析用户态如何进行信号的编程,和信号相关的一些应用。而主要分析,信号在内核中的实现。主要有三个方面的内容。
1、信号相关的内核数据结构
2、发送信号到进程的过程
3、信号处理过程
信号相关的内核数据结构
进程的数据结构task_struct中保持了信号相关的数据结构,一个是pending,这个结构上保存了进程上尚未处理的信号。一个是sighand,这个结构上保存了信号对应的action。
struct sigpending { struct list_head list; sigset_t signal; }; |
struct sigqueue { struct list_head list; int flags; siginfo_t info; struct user_struct *user; }; |
struct sighand_struct { atomic_t count; struct k_sigaction action[_NSIG]; spinlock_t siglock; wait_queue_head_t signalfd_wqh; }; |
发送信号到进程的过程
上面函数调用过程是发送信号到进程的过程。首先find_task_by_vpid通过pid找打对于的进程task_struct数据结构。然后调用send_signal函数进行信号发送,具体过程见下面的分析。
static int send_signal(int sig, struct siginfo *info, struct task_struct *t, int group) { struct sigpending *pending; struct sigqueue *q; trace_sched_signal_send(sig, t); assert_spin_locked(&t->sighand->siglock); /*prepare_signal函数会调用sig_ignored检查信号是否被标记会堵塞 如果被标记为堵塞,就不处理了*/ if (!prepare_signal(sig, t)) return 0; pending = group ? &t->signal->shared_pending : &t->pending; /* * Short-circuit ignored signals and support queuing * exactly one non-rt signal, so that we can get more * detailed information about the cause of the signal. */ if (legacy_queue(pending, sig)) return 0; /* * fast-pathed signals for kernel-internal things like SIGSTOP * or SIGKILL. */ if (info == SEND_SIG_FORCED) goto out_set; /* Real-time signals must be queued if sent by sigqueue, or some other real-time mechanism. It is implementation defined whether kill() does so. We attempt to do so, on the principle of least surprise, but since kill is not allowed to fail with EAGAIN when low on memory we just make sure at least one signal gets delivered and don't pass on the info struct. */ /*分配一个sigqueue 结构图*/ q = __sigqueue_alloc(t, GFP_ATOMIC, (sig < SIGRTMIN && (is_si_special(info) || info->si_code >= 0))); if (q) { /*将sigqueue结构体挂接到task_struct pending队列上面*/ 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); break; } } else if (!is_si_special(info)) { if (sig >= SIGRTMIN && info->si_code != SI_USER) /* * Queue overflow, abort. We may abort if the signal was rt * and sent by user using something other than kill(). */ return -EAGAIN; } out_set: signalfd_notify(t, sig); sigaddset(&pending->signal, sig); /*complete_signal函数唤醒休眠的进程*/ complete_signal(sig, t, group); return 0; } |
下面是唤醒休眠进程进行信号处理的过程,这里要主要,进程如果处于TASK_UNINTERRUPTIBLE状态,则进程不能被唤醒。这也是有时候用kill命令无法杀掉一些休眠的进程的原因。
void signal_wake_up(struct task_struct *t, int resume) { unsigned int mask; /*设置进程为 sigpending状态*/ set_tsk_thread_flag(t, TIF_SIGPENDING); /* * For SIGKILL, we want to wake it up in the stopped/traced/killable * case. We don't check t->state here because there is a race with it * executing another processor and just now entering stopped state. * By using wake_up_state, we ensure the process will wake up and * handle its death signal. */ mask = TASK_INTERRUPTIBLE; if (resume) mask |= TASK_WAKEKILL; /*唤醒状态为 TASK_INTERRUPTIBLE的进程,这里特别要主要的是 TASK_UNINTERRUPTIBLE的进程不会被唤醒*/ if (!wake_up_state(t, mask)) kick_process(t); } |
信号处理过程
进程处理信号的函数是do_signal,调用这个函数的位置是,进程从内核态进入到用户态时会调用这个函数。调用信号的handler在用户态执行,执行完之后通过胶粘代码再次回到内核态来。