信号分为标准信号(standard signals)和实时信号(realtime signals)。标准信号从信号1到31。实时信号从宏SIGRTMIN到SIGRTMAX。在支持NPTL线程的系统上,SIGRTMIN的值是34,SIGRTMAX的值是64。32/33这2个也是实时信号,但是被保留为NPTL内部使用。
每一个信号有3种处置方式(disposition):默认(SIG_DFL)、忽略(SIG_IGN)、用户处理函数。
每个标准信号有个默认的处置动作,如下表:
SIGHUP | 1 | Hang up | term |
SIGINT | 2 | Terminal interrupt | term |
SIGQUIT | 3 | Terminal quit | core |
SIGILL SIGBUS SIGFPE SIGSEGV SIGSYS | 4 7 8 11 31 | Illegal instruction Memory access error Float point error Invalid memory reference Invalid system call | Core dump |
SIGTRAP | 5 | Trace/breakpoint trap | core |
SIGABRT | 6 | abort process | core |
SIGKILL | 9 | sure kill | term |
SIGUSR1 | 10 | term | |
SIGUSR2 | 12 | term | |
SIGPIPE | 13 | Broken pipe | term |
SIGALRM | 14 | timer expired | term |
SIGTERM | 15 | Terminate process | term |
SIGCHLD | 17 | Child terminated or stopped | ignore |
SIGCONT | 18 | continue if stopped | continue |
SIGSTOP | 19 | sure stop | stop |
SIGTSTP | 20 | Terminal stop ( Ctrl+Z) | stop |
SIGTTIN | 21 | Terminal read from BG | stop |
SIGTTOU | 22 | Terminal write from BG | stop |
SIGKILL和SIGSTOP的处置动作不能修改。前者杀死进程、后者停止进程。
信号只有在特定场景下(通常来讲:在kernel和user切换、进程被再次调度执行等等时候)才会被处理(delivery),在这之前信号处于挂起(pending)状态,这个时间通常是非常短暂的。
The signal is handled by (for) the receiving process when it is scheduled to run next time. It is up to the kernel's process scheduler to decide when that will be.
进程如果处于TASK_INTERRUPTIBLE状态,此时如果产生信号,那么进程将被唤醒,执行signal delivery。
进程处于TASK_UNINTERRUPTIBLE状态,信号不能被delivery,直到进程从这个状态返回,在此之前即使SIGKILL也不能杀死这个进程。这有很多弊端,后来Linux添加了一个折中一点的状态TASK_KILLABLE,可以处理信号。
信号也可以被block,此时信号处于pending状态。当信号被unblock的时候,pengding的信号将被delivery,并且按照unblock时刻的disposition处置。
同步信号会立即被delivery(除非被blocked)。那些硬件信号比如非法指令,以及发送给自己的信号都是同步信号。比如kill()自己,在kill()返回之前,信号已经被处理。
信号是可以嵌套执行的,当一个信号处理函数正在执行时,其它信号发生,如果没有被block,那么旧信号的处理函数被打断,新信号的处理函数被执行。
可以设置执行一个信号处理函数时,自动block一些信号(可以是自己),信号处理函数执行完毕后,自动unblock这些信号。这样可以避免嵌套执行。
信号处理函数默认在被中断线程的栈上运行,共享thread-local变量,包括errno,此时可能需要备份还原线程的errno。
标准信号是不支持queue的,同一个信号,如果已经处于pending状态,再次产生此信号,将被忽略。
实时信号支持queue,但是queue有数量限制。
表示多个信号的合集,很多信号接口都需要提供信号合集,数据类型sigset_t。接口如下:
int sigemptyset(sigset_t *set); | 初始化为一个空的set |
int sigfillset(sigset_t *set); | 初始化为一个包含所有信号的set |
int sigaddset(sigset_t *set, int signum); | 添加一个信号到set |
int sigdelset(sigset_t *set, int signum); | 从set删除一个信号 |
int sigismember(const sigset_t *set, int signum); | 判断信号是否包含在set中。 |
上面这些接口返回0表示成功,返回-1表示失败。sigismember返回1表示是成员,0表示不是成员,-1表示失败。
在ubuntu 20上,执行sigfillset,然后用sigismember检测0-64,返回值情况如下:0返回-1,表示不是一个合法的信号。32/33返回0,其它都返回1。
发送信号的接口:
int kill(pid_t pid, int sig);
pid大于0,则发给特定pid的进程。
pid等于0,发给相同进程组的所有进程(包括自己)
pid等于-1,发给所有有权限发送信号的进程,除了1号进程。
pid小于-1,则发给-pid表示的进程组。
如果sig等于0,则不发送信号,而是检查权限和进程是否存在。
发送信号的进程在下列条件之一下才有权限发送信号:
发送进程的real uid等于接收进程的real uid或saved set-user-ID。
发送进程的effective uid等于接收进程的real uid或者saved set-user-ID。
SIGCONT是一个类外,只要是两个进程在同一个session即可。
对于每一个进程,内核维护一个signal mask,表示哪些信号被blocked,发送一个被blocked的信号给进程,那么信号的delivery被延迟,直到这个信号被unblocked。signal mask是一个每线程的属性,多线程可以使用pthread_sigmask()来检查和修改signal mask。一个信号可能以以下方式被添加到signal mask。
一个信号正在执行处理函数时,这个信号被自动添加到signal mask。这个行为依赖与设置信号处理函数时的flags。
当使用sigaction安装一个信号处理函数时,也可以指定自动block的信号集。
使用sigprocmask()维护signal mask。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how的值
SIG_BLOCK,将set添加到signal mask中,
SIG_UNBLOCK,将set从signal mask中删除。
SIG_SETMASK,将set替换旧的signal mask。
当set为NULL时,how被忽略,signal mask不变,可以用来读取signal mask。
如果oldset不为空,则返回之前的signal mask。
fork的时候,子进程继承父进程的signal mask,execv不改变signal mask。
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
act可以为空,用来获取旧的act。oldact可以为空,表示不读取旧的act。
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
sa_mask指定在执行handler时,自动block的信号集。
信号处理函数有两种,sa_handler,sa_sigaction,后者可以获取更多的信息,比如产生信号的进程。在某些架构上,sa_handler和sa_sigaction是一个枚举,不要同时设置着两个值。当sa_flags包含SA_SIGINFO时,使用sa_sigaction。否则使用sa_handler,当设置默认值和忽略时,应该使用sa_handler。
sa_flags还可以包含以下:
SA_NODEFER:不自动block信号自己。
SA_ONSTACK:如果sigaltstack()修改了栈,在这个栈上执行信号处理函数,否则仍然使用默认栈。
SA_RESTART:自动重新开启系统调用,而不是返回-1,EINTR。
sa_restorer没有用到。
int sigpending(sigset_t *set);
查询当前挂起的信号。
int pause(void);
等待信号到来
int sigsuspend(const sigset_t *mask);
将调用线程的signal mask临时替换为mask,然后等待信号处理函数被执行,或者结束进程的信号。信号处理函数执行完毕后sigsuspend才返回。
int sigwait(const sigset_t *set, int *sig);
等待信号变成pending,然后将其从pending中移除。返回signal number。
int sigwaitinfo(const sigset_t *set, siginfo_t *info);
int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout);
和sigwait类型,但是返回更多信息,以及支持超时。
int signalfd(int fd, const sigset_t *mask, int flags);
创建文件描述符,可以读取信号信息。
int sigqueue(pid_t pid, int sig, const union sigval value);
发送实时信号。实时信号的处理和标准信号一样。