1. 信号基本概念
1.1 基本概念
信号是软件中断,用来通知进程发生了异步事件。
信号屏蔽字,当前阻塞而不能递送给该进程的信号集。
内核区分信号传递的两个不同阶段:信号产生和信号传递。
信号产生:内核更新目标进程的数据结构以表示一个信号已被发送。
信号传递:内核强迫目标进程通过信号处理方法对信号做出反应。
挂起信号:已经产生但还没有传递的信号。
1.2 信号产生条件
1)当用户按某些终端键时,引发终端产生信号。
2)硬件异常产生信号,除数0、无效的内存引用等。
3)kill命令、kill函数将信号发送给指定进程或进程组。
4)某种软件条件已发生,并应将其通知有关进程。如管道读端终止,进程写此管道产生SIGPIPE,以及SIGALRM定时器超时。
1.3 信号处理方法
1)忽略此信号。注意SIGKILL和SIGSTOP不能被忽略。
2)捕捉信号。通知内核在某种信号发生时,调用一个用户函数进行处理。
3)执行系统默认动作。大多数信号系统默认动作是终止进程(有些信号在终止进程同时会产生内存映像core文件)。
1.4 常规信号和实时信号
Linux 传统的信号 1~31 为常规信号(regular signal),POSIX 引入了实时信号(real-time signal)编号为 32~64。
它们的不同在于:常规信号同一个编号在 pending 队列中只存在一份,如果有重复的则直接丢弃;实时信号的多个相同信号不能丢弃,需要保证每个信号都能送达。
Linux 常规信号介绍如下。
编号 | 信号名称 | 缺省操作 | 解释 | POSIX |
---|---|---|---|---|
1 | SIGHUP | Terminate | Hang up controlling terminal or process | Yes |
2 | SIGINT | Terminate | Interrupt from keyboard | Yes |
3 | SIGQUIT | Dump | Quit from keyboard | Yes |
4 | SIGILL | Dump | Illegal instruction | Yes |
5 | SIGTRAP | Dump | Breakpoint for debugging | No |
6 | SIGABRT | Dump | Abnormal termination | Yes |
6 | SIGIOT | Dump | Equivalent to SIGABRT | No |
7 | SIGBUS | Dump | Bus error | No |
8 | SIGFPE | Dump | Floating-point exception | Yes |
9 | SIGKILL | Terminate | Forced-process termination | Yes |
10 | SIGUSR1 | Terminate | Available to processes | Yes |
11 | SIGSEGV | Dump | Invalid memory reference | Yes |
12 | SIGUSR2 | Terminate | Available to processes | Yes |
13 | SIGPIPE | Terminate | Write to pipe with no readers | Yes |
14 | SIGALRM | Terminate | Real-timerclock | Yes |
15 | SIGTERM | Terminate | Process termination | Yes |
16 | SIGSTKFLT | Terminate | Coprocessor stack error | No |
17 | SIGCHLD | Ignore | Child process stopped or terminated, or got signal if traced | Yes |
18 | SIGCONT | Continue | Resume execution, if stopped | Yes |
19 | SIGSTOP | Stop | Stop process execution | Yes |
20 | SIGTSTP | Stop | Stop process issued from tty | Yes |
21 | SIGTTIN | Stop | Background process requires input | Yes |
22 | SIGTTOU | Stop | Background process requires output | Yes |
23 | SIGURG | Ignore | Urgent condition on socket | No |
24 | SIGXCPU | Dump | CPU time limit exceeded | No |
25 | SIGXFSZ | Dump | File size limit exceeded | No |
26 | SIGVTALRM | Terminate | Virtual timer clock | No |
27 | SIGPROF | Terminate | Profile timer clock | No |
28 | SIGWINCH | Ignore | Window resizing | No |
29 | SIGIO | Terminate | I/O now possible | No |
29 | SIGPOLL | Terminate | Equivalent to SIGIO | No |
30 | SIGPWR | Terminate | Power supply failure | No |
31 | SIGSYS | Dump | Bad system call | No |
31 | SIGUNUSED | Dump | Equivalent to SIGSYS | No |
1.5 信号处理过程
1.6 POSIX 信号和多线程
POSIX 标准对多线程应用的信号处理有一些严格的要求:
1)信号处理程序必须在多线程应用的所有线程之前共享;不过,每个线程必须有自己的挂起信号掩码和阻塞信号掩码。
2) POSIX库函数kill和sigqueue必须向所有的多线程应用而不是某个特殊线程发送信号,有内核产生的信号同样如此。
3)每个发送给多线程应用的信号仅传送给一个线程,这个线程是由内核在从不会阻塞该信号的线程中随意选择出来的。
4)如果多线程应用发送了一个致命的信号,那么内核将杀死该应用的所有线程,而不仅仅是杀死接收信号的那个线程。
1.7 信号集相关操作
信号集用来描述信号的集合,每个信号占用一位。
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t;
int sigemptyset(sigset_t *set); /* 初始化由 set 指向的信号集,清除其中所有信号。 */
int sigfillset(sigset_t *set); /* 初始化由 set 指向的信号集,使其包含所有信号。 */
int sigaddset(sigset_t *set, int signo); /* 将一个信号 signo 添加到现有信号集 set 中。 */
int sigdelset(sigset_t *set, int signo); /* 将一个信号 signo 从信号集 set 中删除 */
int sigismember(const sigset_t *set, int signo); /* 判断指定信号 signo 是否在信号集 set 中。 */
1.8 进程信号相关操作
kill
kill 系统调用的功能是发送一个信号给线程组,只需要线程组挑出一个线程来响应处理信号。但是对于致命信号,线程组内所有进程都会被杀死,而不仅仅是处理信号的线程。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
pid:可能选择有以下四种
1)pid大于零时,pid是信号欲送往的进程的标识。
2)pid等于零时,信号将送往所有与调用kill()的那个进程属同一个使用组的进程。
3)pid等于-1时,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)。
4)pid小于-1时,信号将送往以-pid为组标识的进程。
sig:准备发送的信号代码,假如其值为零则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为零来检验某个进程是否仍在执行。
返回值说明: 成功,返回0。失败返回-1,errno被设为以下的某个值。
EINVAL:指定的信号码无效(参数 sig 不合法)
EPERM;权限不够无法传送信号给指定进程
ESRCH:参数 pid 所指定的进程或进程组不存在
tkill
向指定线程发送信号
int tkill(int tid, int sig);
tgkill:
向指定进程中的线程发送信号
int tgkill(int tgid, int tid, int sig);
sigqueue
sigqueue 是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数 sigaction 配合使用。
#include <sys/types.h>
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval val)
第一个参数是指定接收信号的进程ID;
第二个参数确定即将发送的信号;
第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;
成功,返回 0;失败,返回 -1。
sigqueue 只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查 pid 的有效性以及当前进程是否有权限向目标进程发送信号。
sigqueue 比 kill 传递了更多的附加信息,在调用 sigqueue 时,sigval_t 指定的信息会拷贝到对应 sig 注册的3参数信号处理函数的 siginfo_t 结构中,这样信号处理函数就可以处理这些信息了。
由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。
signal
注册信号的用户态处理函数。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
第一个参数指定信号的值;
第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。
成功,返回handler值,失败,返回SIG_ERR。
sigaction
注册信号的用户态处理函数
#include <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
第一个参数为信号的值;
第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;
第三个参数oldact指向的对象用来保存返回的原来对相应信号的处理,可指定oldact为NULL。
如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。
第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉的信号等。
sigaction结构定义如下:
struct sigaction {
union{
__sighandler_t _sa_handler;
void (*_sa_sigaction)(int, struct siginfo *, void *);
}_u
sigset_t sa_mask;
unsigned long sa_flags;
void (*sa_restorer)(void);
}
1)联合数据结构中的两个元素_sa_handler以及*_sa_sigaction指定信号关联函数,即用户指定的信号处理函数。除了可以是用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式),也可以为SIG_IGN(忽略信号)。
2)由_sa_sigaction是指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),它指定一个3参数信号处理函数。
第一个参数为信号值,
第三个参数没有使用,
第二个参数是指向siginfo_t结构的指针,结构中包含信号携带的数据值,参数所指向的结构如下:
siginfo_t {
int si_signo; /* 信号值,对所有信号有意义*/
int si_errno; /* errno值,对所有信号有意义*/
int si_code; /* 信号产生的原因,对所有信号有意义*/
union{ /* 联合数据结构,不同成员适应不同信号 */
//确保分配足够大的存储空间
int _pad[SI_PAD_SIZE];
/* 对SIGKILL有意义的结构 */
struct{
__kernel_pid_t _pid; /* sender's pid */
__ARCH_SI_UID_T _uid; /* sender's uid */
}_kill;
... ...
... ...
/* 对POSIX.1b信号有意义的结构 */
struct{
__kernel_pid_t _pid; /* sender's pid */
__ARCH_SI_UID_T _uid; /* sender's uid */
sigval_t _sigval;
} _rt;
/* 对SIGILL, SIGFPE, SIGSEGV, SIGBUS有意义的结构 */
struct{
...
}...
... ...
}
}
前面在讨论系统调用 sigqueue 发送信号时,sigqueue 的第三个参数就是 sigval 联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。
3)sa_mask 指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定 SA_NODEFER 或者 SA_NOMASK 标志位。
请注意sa_mask指定的信号阻塞的前提条件,是在由 sigaction 安装信号的处理函数执行过程中由 sa_mask指定的信号才被阻塞。
4)sa_flags 中标志位及含义如下表所示。
标志 | 说明 |
---|---|
SA_NOCLDSTOP | 假如signum的值是SIGCHLD,则在子进程停止或恢复执行时不会传信号给调用本系统调用的进程。 |
SA_NOCLDWAIT | 当调用此系统调用的进程之子进程终止时,系统不会建立zombie进程。 |
SA_SIGINFO | 指定信号处理函数需要三个参数,所以应使用sa_sigaction替sa_handler。 |
SA_RESTART | 内核会自动重启信号中断的系统调用,否则返回EINTR错误值。 |
SA_NODEFER | 在信号处理函数处置信号的时段中,内核不会把这个间隙中产生的信号阻塞。 |
SA_RESETHAND | 信号处理函数接收到信号后,会先将对信号处理的方式设为预设方式,而且当函数处理该信号时,后来发生的信号将不会被阻塞。 |
SA_ONSTACK | 若利用sigaltstack()建立信号专用堆栈,则此标志会把所有信号送往该堆栈。 |
5)sa_restorer 不再使用。
sigprocmask
调用 sigprocmask 函数可以检测或者设置进程的信号屏蔽字。
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
成功,返回0,失败,返回-1。
若 oset 参数是一个非空指针,则进程的当前信号屏蔽字将通过 oset 返回。
若 set 参数是一个非空指针,则参数 how 将指示如何修改当前信号屏蔽字。
how 的可选值如下表所示。
how | 说明 |
---|---|
SIG_BLOCK | 该进程新的信号屏蔽字是其当前信号屏蔽字和 set 指向信号集的并集。 |
SIG_UNBLOCK | 该进程的信号屏蔽字是当前信号屏蔽字和 set 所指向信号集补集的交集。set 包含了我们希望解除阻塞的信号。 |
SIG_SETMASK | 该进程新的信号屏蔽字设置为 set 所指向的信号集。 |
sigpending
通过 set 参数返回当前进程所有未决的信号集。
#include <signal.h>
int sigpending(sigset_t *set);
成功,返回0,失败,返回-1。
sigsuspend
用于在接收到某个信号之前,临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。
#include <signal.h>
int sigsuspend(const sigset_t *mask));
sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。
该系统调用始终返回-1,并将 errno 设置为 EINTR。
1.9 线程信号相关操作
pthread_kill
将信号发送给同一进程内指定的线程(包括自己)。
#include <signal.h>
int pthread_kill(pthread_t thread, int signo);
成功,返回0,失败,返回错误编号:
ESRCH:指定线程不存在;
EIINVAL:指定信号无效或不支持。
若signo为0(空信号),则执行错误检查并返回ESRCH,但不发送信号,用于判断指定线程是否存在。
sigwait
线程同步等待一个或多个信号发生。
#include <signal.h>
int sigwait(const sigset_t *restrict sigset, int *restrict signop);
第一个参数sigset指定线程等待的信号集;
第二个参数signop指向的整数表明接收到的信号值。
成功,返回0,并将接收到的信号值存入signop所指向的内存空间,失败,返回错误编号(errno)。
该函数将调用线程挂起,直到信号集中的任何一个信号被递送。该函数接收递送的信号后,将其从未决队列中移除(以防返回时信号被signal/sigaction安装的处理函数捕获),然后唤醒线程并返回。
若已阻塞等待信号集中的信号,则 sigwait 会自动解除信号集的阻塞状态,直到有新的信号被递送。在返回之前,sigwait 将恢复线程的信号屏蔽字。因此,sigwait 并不改变信号的阻塞状态。
sigwaitinfo
线程同步等待一个或多个信号发生。
#include <signal.h>
int sigwaitinfo(const sigset_t *set, siginfo_t *info);
sigwaitinfo功能与sigwait类似,但在返回值上存在差别。
sigwait在signop中返回触发的信号值,而sigwaitinfo的返回值就是触发的信号值,并且如果info不为NULL,则sigwaitinfo返回时,还会在siginfo_t *info中返回更多该信号的信息,
sigtimedwait
带有超时机制同步等待一个或多个信号发生。
#include <signal.h>
int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout);
sigtimedwait的行为与sigwaitinfo的行为类似,只是它多了一个超时参数timeout,也就是可以设置该函数的最大阻塞时间。
pthread_sigmask。
设置线程的信号屏蔽集。
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
成功,返回0,失败,返回错误编码。
若参数 oset 为非空指针,则该指针返回调用前本线程的信号屏蔽字;
若参数 set 为非空指针,则参数how指示如何修改当前信号屏蔽字;
how参数可选值如下表所示。
how | 说明 |
---|---|
SIG_BLOCK | 将set中包含的信号加入本线程的当前信号屏蔽字。 |
SIG_UNBLOCK | 从本线程的当前信号屏蔽字中移除set中包含的信号(哪怕该信号并未被阻塞)。 |
SIG_SETMASK | 将set指向的信号集设置为本线程的信号屏蔽字。 |
主线程调用pthread_sigmask()设置信号屏蔽字后,其创建的新线程将继承主线程的信号屏蔽字。然而,新线程对信号屏蔽字的更改不会影响创建者和其他线程。
通常,被阻塞的信号将不能中断本线程的执行,除非该信号指示致命的程序错误(如SIGSEGV)。此外,不能被忽略处理的信号(SIGKILL 和SIGSTOP )无法被阻塞。
2. 信号内核实现
信号在目标进程中注册
在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。如果发送给一个处于可运行状态的进程,则只置相应的域即可。
进程的task_struct结构中有关于本进程中未决信号的数据成员: struct sigpending pending:
struct sigpending{
struct sigqueue *head, *tail;
sigset_t signal;
};
第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为”未决信号信息链”)的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:
struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
2.1 信号发送
SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
{
struct siginfo info;
info.si_signo = sig;
info.si_errno = 0;
info.si_code = SI_USER;
info.si_pid = task_tgid_vnr(current);
info.si_uid = current_uid();
return kill_something_info(sig, &info, pid);
}
static int kill_something_info(int sig, struct siginfo *info, pid_t pid)
{
int ret;
/* 发送信号给特定进程的线程组 */
if (pid > 0) {
rcu_read_lock();
ret = kill_pid_info(sig, info, find_vpid(pid));
rcu_read_unlock();
return ret;
}
read_lock(&tasklist_lock);
/* 发送信号给pid进程或本进程同组进程的所有线程组 */
if (pid != -1) {
ret = __kill_pgrp_info(sig, info,
pid ? find_vpid(-pid) : task_pgrp(current));
} else {
/* 发送信号给所有进程,除swapper、init、current进程 */
int retval = 0, count = 0;
struct task_struct * p;
for_each_process(p) {
if (task_pid_vnr(p) > 1 &&
!same_thread_group(p, current)) {
int err = group_send_sig_info(sig, info, p);
++count;
if (err != -EPERM)
retval = err;
}
}
ret = count ? retval : -ESRCH;
}
read_unlock(&tasklist_lock);
return ret;
}
int kill_pid_info(int sig, struct siginfo *info, struct pid *pid)
{
int error = -ESRCH;
struct task_struct *p;
rcu_read_lock();
retry:
p = pid_task(pid, PIDTYPE_PID);
if (p) {
error = group_send_sig_info(sig, info, p);
if (unlikely(error == -ESRCH))
/*
* The task was unhashed in between, try again.
* If it is dead, pid_task() will return NULL,
* if we race with de_thread() it will find the
* new leader.
*/
goto retry;
}
rcu_read_unlock();
return error;
}
/*
* send signal info to all the members of a group
* 向整个线程组发送信号
*/
int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
int ret;
rcu_read_lock();
/* 检查权限 */
ret = check_kill_permission(sig, info, p);
rcu_read_unlock();
if (!ret && sig)
ret = do_send_sig_info(sig, info, p, true);
return ret;
}
int do_send_sig_info(int sig, struct siginfo *info, struct task_struct *p,
bool group)
{
unsigned long flags;
int ret = -ESRCH;
if (lock_task_sighand(p, &flags)) {
ret = send_signal(sig, info, p, group);
unlock_task_sighand(p, &flags);
}
return ret;
}
static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
int group)
{
int from_ancestor_ns = 0;
#ifdef CONFIG_PID_NS
from_ancestor_ns = si_fromuser(info) &&
!task_pid_nr_ns(current, task_active_pid_ns(t));
#endif
return __send_signal(sig, info, t, group, from_ancestor_ns);
}
static int __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;
trace_signal_generate(sig, info, t);
assert_spin_locked(&t->sighand->siglock);
/* 检查是否忽略信号 */
if (!prepare_signal(sig, t, from_ancestor_ns))
return 0;
/* 选择信号挂起队列:线程组共享队列 or 线程私有队列 */
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.
* SEND_SIG_NOINFO : 表示信号由用户态进程发送;
* SEND_SIG_PRIV : 表示信号由内核态( 进程) 发送;
* SEND_SIG_FORCED : 表示信号由内核态( 进程) 发送, 并且信号是SIGKILL 或者SIGSTOP.
* 强制信号,则直接快速处理
*/
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.
*/
if (sig < SIGRTMIN)
override_rlimit = (is_si_special(info) || info->si_code >= 0);
else
override_rlimit = 0;
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) {
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;
}
userns_fixup_signal_uid(&q->info, t);
} 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().
*/
trace_signal_overflow_fail(sig, group, info);
return -EAGAIN;
} else {
/*
* This is a silent loss of information. We still
* send the signal, but the *info bits are lost.
*/
trace_signal_lose_info(sig, group, info);
}
}
out_set:
signalfd_notify(t, sig);
/* 更新pending队列中对应bit */
sigaddset(&pending->signal, sig);
/* 查找能接收信号的线程 */
complete_signal(sig, t, group);
return 0;
}
static int prepare_signal(int sig, struct task_struct *p, int from_ancestor_ns)
{
struct signal_struct *signal = p->signal;
struct task_struct *t;
if (unlikely(signal->flags & SIGNAL_GROUP_EXIT)) {
/*
* The process is in the middle of dying, nothing to do.
* 若进程正在退出,则什么也不做
*/
} else if (sig_kernel_stop(sig)) {
/*
* This is a stop signal. Remove SIGCONT from all queues.
* 若是stop信号,则移除已有的continue信号
*/
rm_from_queue(sigmask(SIGCONT), &signal->shared_pending);
t = p;
do {
rm_from_queue(sigmask(SIGCONT), &t->pending);
} while_each_thread(p, t);
} else if (sig == SIGCONT) {
unsigned int why;
/*
* Remove all stop signals from all queues, wake all threads.
* 若是continue信号,则移除已有stop信号,并唤醒所有进程
*/
rm_from_queue(SIG_KERNEL_STOP_MASK, &signal->shared_pending);
t = p;
do {
task_clear_jobctl_pending(t, JOBCTL_STOP_PENDING);
rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending);
if (likely(!(t->ptrace & PT_SEIZED)))
wake_up_state(t, __TASK_STOPPED);
else
ptrace_trap_notify(t);
} while_each_thread(p, t);
/*
* Notify the parent with CLD_CONTINUED if we were stopped.
*
* If we were in the middle of a group stop, we pretend it
* was already finished, and then continued. Since SIGCHLD
* doesn't queue we report only CLD_STOPPED, as if the next
* CLD_CONTINUED was dropped.
*/
why = 0;
if (signal->flags & SIGNAL_STOP_STOPPED)
why |= SIGNAL_CLD_CONTINUED;
else if (signal->group_stop_count)
why |= SIGNAL_CLD_STOPPED;
if (why) {
/*
* The first thread which returns from do_signal_stop()
* will take ->siglock, notice SIGNAL_CLD_MASK, and
* notify its parent. See get_signal_to_deliver().
*/
signal->flags = why | SIGNAL_STOP_CONTINUED;
signal->group_stop_count = 0;
signal->group_exit_code = 0;
}
}
return !sig_ignored(p, sig, from_ancestor_ns);
}
static int sig_ignored(struct task_struct *t, int sig, int from_ancestor_ns)
{
/*
* Blocked signals are never ignored, since the
* signal handler may change by the time it is
* unblocked.
* 被阻塞的信号将不会被忽略
*/
if (sigismember(&t->blocked, sig) || sigismember(&t->real_blocked, sig))
return 0;
if (!sig_task_ignored(t, sig, from_ancestor_ns))
return 0;
/*
* Tracers may want to know about even ignored signals.
* 进程没有被跟踪,则忽略
*/
return !t->ptrace;
}
static int sig_task_ignored(struct task_struct *t, int sig,
int from_ancestor_ns)
{
void __user *handler;
/* 获取信号处理函数 */
handler = sig_handler(t, sig);
/* 信号是默认处理,进程无法被杀死的,则忽略信号 */
if (unlikely(t->signal->flags & SIGNAL_UNKILLABLE) &&
handler == SIG_DFL && !from_ancestor_ns)
return 1;
return sig_handler_ignored(handler, sig);
}
static int sig_handler_ignored(void __user *handler, int sig)
{
/* Is it explicitly or implicitly ignored? */
/* SIG_IGN显示忽略信号,SIG_DEL且为SIGCONT、SIGCHLD、SIGWINCH、SIGURG内核隐式忽略信号 */
return handler == SIG_IGN ||
(handler == SIG_DFL && sig_kernel_ignore(sig));
}
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.
*/
/* 判断目标进程p是否合适 */
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.
* 该信号是否是发送给一个线程组并且该线程组不为空
*/
return;
else {
/*
* Otherwise try to find a suitable thread.
*/
t = signal->curr_target;
while (!wants_signal(sig, t)) {
t = next_thread(t);
if (t == signal->curr_target)
/*
* No thread needs to be woken.
* Any eligible threads will see
* the signal in the queue soon.
*/
return;
}
signal->curr_target = t;
}
/*
* Found a killable thread. If the signal will be fatal,
* then start taking the whole group down immediately.
* 判断该信号是否是致命信号,如果是的话就添加SIGKILL信号给每一个线程,并且唤醒它来处理
*/
if (sig_fatal(p, sig) &&
!(signal->flags & (SIGNAL_UNKILLABLE | SIGNAL_GROUP_EXIT)) &&
!sigismember(&t->real_blocked, sig) &&
(sig == SIGKILL || !t->ptrace)) {
/*
* This signal will be fatal to the whole group.
*/
if (!sig_kernel_coredump(sig)) {
/*
* Start a group exit and wake everybody up.
* This way we don't have other threads
* running and doing things after a slower
* thread has the fatal signal pending.
*/
signal->flags = SIGNAL_GROUP_EXIT;
signal->group_exit_code = sig;
signal->group_stop_count = 0;
t = p;
do {
task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
sigaddset(&t->pending.signal, SIGKILL);
signal_wake_up(t, 1);
} while_each_thread(p, t);
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;
}
/*
* 判断是否能够发送信号给该进程,
* 1.在以下情况下无法发送该信号给进程:
* a.信号被进程阻塞
* b.进程正在退出
* c.进程处于暂停状态
* 2.在以下情况下能够发送信号给进程:
* a.信号是SIGKILL,必须发送
* b.进程运行在当前CPU上
* c.进程当前没有挂起的信号
*/
static inline int wants_signal(int sig, struct task_struct *p)
{
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);
}
/*
* Tell a process that it has a new active signal..
*
* NOTE! we rely on the previous spin_lock to
* lock interrupts for us! We can only be called with
* "siglock" held, and the local interrupt must
* have been disabled when that got acquired!
*
* No need to set need_resched since signal event passing
* goes through ->blocked
*/
void signal_wake_up(struct task_struct *t, int resume)
{
unsigned int mask;
/* 把标志TIF_SIGPENDING 加到thread_info->flags 中*/
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;
/* 唤醒目标线程 */
if (!wake_up_state(t, mask))
kick_process(t);
}
2.2 信号处理
信号处理的关键是信号的响应时机,对一个进程发送一个信号以后,只是简单把信号挂载到目标进程的信号 pending 队列上去,信号真正得到执行的时机是进程在内核态执行完异常/中断返回到用户态的时刻。
arch/arm/kernel/entry-common.S
/*
* This is the fast syscall return path. We do as little as
* possible here, and this includes saving r0 back into the SVC
* stack.
*/
ret_fast_syscall:
UNWIND(.fnstart )
UNWIND(.cantunwind )
disable_irq @ disable interrupts
ldr r1, [tsk, #TI_FLAGS]
tst r1, #_TIF_WORK_MASK
bne fast_work_pending
#if defined(CONFIG_IRQSOFF_TRACER)
asm_trace_hardirqs_on
#endif
/* perform architecture specific actions before user return */
arch_ret_to_user r1, lr
restore_user_regs fast = 1, offset = S_OFF
UNWIND(.fnend )
/*
* Ok, we need to do extra processing, enter the slow path.
*/
fast_work_pending:
str r0, [sp, #S_R0+S_OFF]! @ returned r0
work_pending:
tst r1, #_TIF_NEED_RESCHED
bne work_resched
tst r1, #_TIF_SIGPENDING|_TIF_NOTIFY_RESUME
beq no_work_pending
mov r0, sp @ 'regs'
mov r2, why @ 'syscall'
tst r1, #_TIF_SIGPENDING @ delivering a signal?
movne why, #0 @ prevent further restarts
bl do_notify_resume
b ret_slow_syscall @ Check work again
work_resched:
bl schedule
/*
* "slow" syscall return path. "why" tells us if this was a real syscall.
*/
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq @ disable interrupts
ENTRY(ret_to_user_from_irq)
ldr r1, [tsk, #TI_FLAGS]
tst r1, #_TIF_WORK_MASK
bne work_pending
no_work_pending:
#if defined(CONFIG_IRQSOFF_TRACER)
asm_trace_hardirqs_on
#endif
/* perform architecture specific actions before user return */
arch_ret_to_user r1, lr
restore_user_regs fast = 0, offset = 0
ENDPROC(ret_to_user_from_irq)
ENDPROC(ret_to_user)
asmlinkage void
do_notify_resume(struct pt_regs *regs, unsigned int thread_flags, int syscall)
{
if (thread_flags & _TIF_SIGPENDING)
do_signal(regs, syscall);
if (thread_flags & _TIF_NOTIFY_RESUME) {
clear_thread_flag(TIF_NOTIFY_RESUME);
tracehook_notify_resume(regs);
if (current->replacement_session_keyring)
key_replace_session_keyring();
}
}
static void do_signal(struct pt_regs *regs, int syscall)
{
unsigned int retval = 0, continue_addr = 0, restart_addr = 0;
struct k_sigaction ka;
siginfo_t info;
int signr;
/*
* We want the common case to go fast, which
* is why we may in certain cases get here from
* kernel mode. Just return without doing anything
* if so.
*/
if (!user_mode(regs))
return;
/*
* If we were from a system call, check for system call restarting...
* 如果是系统调用被中断,检查是否需要重启系统调用
*/
if (syscall) {
continue_addr = regs->ARM_pc;
restart_addr = continue_addr - (thumb_mode(regs) ? 2 : 4);
retval = regs->ARM_r0;
/*
* Prepare for system call restart. We do this here so that a
* debugger will see the already changed PSW.
*/
switch (retval) {
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
regs->ARM_r0 = regs->ARM_ORIG_r0;
regs->ARM_pc = restart_addr;
break;
case -ERESTART_RESTARTBLOCK:
regs->ARM_r0 = -EINTR;
break;
}
}
if (try_to_freeze())
goto no_signal;
/*
* Get the signal to deliver. When running under ptrace, at this
* point the debugger may change all our registers ...
* 从线程的信号 pending 队列中取出信号,如果没有对应的用户自定义处理函数,则执行默认的内核态处理函数。
*/
signr = get_signal_to_deliver(&info, &ka, regs, NULL);
if (signr > 0) {
sigset_t *oldset;
/*
* Depending on the signal settings we may need to revert the
* decision to restart the system call. But skip this if a
* debugger has chosen to restart at a different PC.
*/
if (regs->ARM_pc == restart_addr) {
if (retval == -ERESTARTNOHAND
|| (retval == -ERESTARTSYS
&& !(ka.sa.sa_flags & SA_RESTART))) {
regs->ARM_r0 = -EINTR;
regs->ARM_pc = continue_addr;
}
}
if (test_thread_flag(TIF_RESTORE_SIGMASK))
oldset = ¤t->saved_sigmask;
else
oldset = ¤t->blocked;
/* 执行信号处理函数 */
if (handle_signal(signr, &ka, &info, oldset, regs) == 0) {
/*
* A signal was successfully delivered; the saved
* sigmask will have been stored in the signal frame,
* and will be restored by sigreturn, so we can simply
* clear the TIF_RESTORE_SIGMASK flag.
*/
if (test_thread_flag(TIF_RESTORE_SIGMASK))
clear_thread_flag(TIF_RESTORE_SIGMASK);
}
return;
}
no_signal:
if (syscall) {
/*
* Handle restarting a different system call. As above,
* if a debugger has chosen to restart at a different PC,
* ignore the restart.
*/
if (retval == -ERESTART_RESTARTBLOCK
&& regs->ARM_pc == continue_addr) {
if (thumb_mode(regs)) {
regs->ARM_r7 = __NR_restart_syscall - __NR_SYSCALL_BASE;
regs->ARM_pc -= 2;
} else {
#if defined(CONFIG_AEABI) && !defined(CONFIG_OABI_COMPAT)
regs->ARM_r7 = __NR_restart_syscall;
regs->ARM_pc -= 4;
#else
u32 __user *usp;
regs->ARM_sp -= 4;
usp = (u32 __user *)regs->ARM_sp;
if (put_user(regs->ARM_pc, usp) == 0) {
regs->ARM_pc = KERN_RESTART_CODE;
} else {
regs->ARM_sp += 4;
force_sigsegv(0, current);
}
#endif
}
}
/* If there's no signal to deliver, we just put the saved sigmask
* back.
* 恢复保存的信号掩码
*/
if (test_thread_flag(TIF_RESTORE_SIGMASK)) {
clear_thread_flag(TIF_RESTORE_SIGMASK);
sigprocmask(SIG_SETMASK, ¤t->saved_sigmask, NULL);
}
}
}
int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,
struct pt_regs *regs, void *cookie)
{
struct sighand_struct *sighand = current->sighand;
struct signal_struct *signal = current->signal;
int signr;
relock:
/*
* We'll jump back here after any time we were stopped in TASK_STOPPED.
* While in TASK_STOPPED, we were considered "frozen enough".
* Now that we woke up, it's crucial if we're supposed to be
* frozen that we freeze now before running anything substantial.
*/
try_to_freeze();
spin_lock_irq(&sighand->siglock);
/*
* Every stopped thread goes here after wakeup. Check to see if
* we should notify the parent, prepare_signal(SIGCONT) encodes
* the CLD_ si_code into SIGNAL_CLD_MASK bits.
* 子进程状态变化时,发送信号给父进程
*/
if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {
int why;
if (signal->flags & SIGNAL_CLD_CONTINUED)
why = CLD_CONTINUED;
else
why = CLD_STOPPED;
signal->flags &= ~SIGNAL_CLD_MASK;
spin_unlock_irq(&sighand->siglock);
/*
* Notify the parent that we're continuing. This event is
* always per-process and doesn't make whole lot of sense
* for ptracers, who shouldn't consume the state via
* wait(2) either, but, for backward compatibility, notify
* the ptracer of the group leader too unless it's gonna be
* a duplicate.
*/
read_lock(&tasklist_lock);
do_notify_parent_cldstop(current, false, why);
if (ptrace_reparented(current->group_leader))
do_notify_parent_cldstop(current->group_leader,
true, why);
read_unlock(&tasklist_lock);
goto relock;
}
for (;;) {
struct k_sigaction *ka;
if (unlikely(current->jobctl & JOBCTL_STOP_PENDING) &&
do_signal_stop(0))
goto relock;
if (unlikely(current->jobctl & JOBCTL_TRAP_MASK)) {
do_jobctl_trap();
spin_unlock_irq(&sighand->siglock);
goto relock;
}
/* 从pending队列中,取出优先级最高信号 */
signr = dequeue_signal(current, ¤t->blocked, info);
if (!signr)
break; /* will return 0 */
if (unlikely(current->ptrace) && signr != SIGKILL) {
signr = ptrace_signal(signr, info,
regs, cookie);
if (!signr)
continue;
}
/* 取出信号处理函数 */
ka = &sighand->action[signr-1];
/* Trace actually delivered signals. */
trace_signal_deliver(signr, info, ka);
/* 信号处理方法:忽略 */
if (ka->sa.sa_handler == SIG_IGN) /* Do nothing. */
continue;
/* 用户态处理函数 */
if (ka->sa.sa_handler != SIG_DFL) {
/* Run the handler. */
*return_ka = *ka;
if (ka->sa.sa_flags & SA_ONESHOT)
ka->sa.sa_handler = SIG_DFL;
break; /* will return non-zero "signr" value */
}
/*
* Now we are doing the default action for this signal.
* 内核态默认处理方法,默认忽略
*/
if (sig_kernel_ignore(signr)) /* Default is nothing. */
continue;
/*
* Global init gets no signals it doesn't want.
* Container-init gets no signals it doesn't want from same
* container.
*
* Note that if global/container-init sees a sig_kernel_only()
* signal here, the signal must have been generated internally
* or must have come from an ancestor namespace. In either
* case, the signal cannot be dropped.
*/
if (unlikely(signal->flags & SIGNAL_UNKILLABLE) &&
!sig_kernel_only(signr))
continue;
/* 默认处理方法STP:SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU */
if (sig_kernel_stop(signr)) {
/*
* The default action is to stop all threads in
* the thread group. The job control signals
* do nothing in an orphaned pgrp, but SIGSTOP
* always works. Note that siglock needs to be
* dropped during the call to is_orphaned_pgrp()
* because of lock ordering with tasklist_lock.
* This allows an intervening SIGCONT to be posted.
* We need to check for that and bail out if necessary.
*/
if (signr != SIGSTOP) {
spin_unlock_irq(&sighand->siglock);
/* signals can be posted during this window */
if (is_current_pgrp_orphaned())
goto relock;
spin_lock_irq(&sighand->siglock);
}
if (likely(do_signal_stop(info->si_signo))) {
/* It released the siglock. */
goto relock;
}
/*
* We didn't actually stop, due to a race
* with SIGCONT or something like that.
*/
continue;
}
spin_unlock_irq(&sighand->siglock);
/*
* Anything else is fatal, maybe with a core dump.
*/
current->flags |= PF_SIGNALED;
/* 默认处理方法COREDUMP */
if (sig_kernel_coredump(signr)) {
if (print_fatal_signals)
print_fatal_signal(regs, info->si_signo);
/*
* If it was able to dump core, this kills all
* other threads in the group and synchronizes with
* their demise. If we lost the race with another
* thread getting here, it set group_exit_code
* first and our do_group_exit call below will use
* that value and ignore the one we pass it.
*/
do_coredump(info->si_signo, info->si_signo, regs);
}
/*
* Death signals, no core dump.
* 默认处理方法Terminate
*/
do_group_exit(info->si_signo);
/* NOTREACHED */
}
spin_unlock_irq(&sighand->siglock);
return signr;
}
static int
handle_signal(unsigned long sig, struct k_sigaction *ka,
siginfo_t *info, sigset_t *oldset,
struct pt_regs * regs)
{
struct thread_info *thread = current_thread_info();
struct task_struct *tsk = current;
int usig = sig;
int ret;
/*
* translate the signal
*/
if (usig < 32 && thread->exec_domain && thread->exec_domain->signal_invmap)
usig = thread->exec_domain->signal_invmap[usig];
/*
* Set up the stack frame
* 构造返回堆栈,将用户态返回地址替换成用户注册的信号处理函数
*/
if (ka->sa.sa_flags & SA_SIGINFO)
ret = setup_rt_frame(usig, ka, info, oldset, regs);
else
ret = setup_frame(usig, ka, oldset, regs);
/*
* Check that the resulting registers are actually sane.
*/
ret |= !valid_user_regs(regs);
if (ret != 0) {
force_sigsegv(sig, tsk);
return ret;
}
/*
* Block the signal if we were successful.
* 阻塞当前信号
*/
spin_lock_irq(&tsk->sighand->siglock);
sigorsets(&tsk->blocked, &tsk->blocked,
&ka->sa.sa_mask);
if (!(ka->sa.sa_flags & SA_NODEFER))
sigaddset(&tsk->blocked, sig);
recalc_sigpending();
spin_unlock_irq(&tsk->sighand->siglock);
return 0;
}