Linux信号机制是进程间消息传递的一种方法,是在软件层次对中断机制的一种模拟,即软中断。
1. 信号分类
信号可以分为可靠信号和不可靠信号,或者实时信号和非实时信号。从UNIX系统继承过来的信号都是不可靠信号,信号值为[1,SIGRTMIN],表现在信号不支持排队,如果同一个信号已经有挂起的,新触发的信号就会被丢弃。后来Linux改进了信号机制,增加了32种信号[SIGRTMIN,SIGRTMAX],这些信号都是可靠的信号,表现在信号支持排队,不会丢失。信号的性质和调用的信号安装、发送接口无关。
2. 信号响应时机
Signal的响应时间点是进程从内核态返回用户态时(系统调用、中断),运行在进程上下文(用户态)。
1. 用户线程运行在用户态。立刻打断当前线程的运行,执行用户自定义的信号处理函数。
2. 用户线程运行在内核态。不会打断当前的执行流程,等到从内核态返回用户态的时候,先处理超时信号,在当前线程上下文运行用户注册的信号处理函数,然后再返回用户态执行之前的流程。
3. 组塞在系统调用。
1)可被中断的系统调用。打断当前的阻塞状态返回用户态,在返回用户态时先处理超时信号,在当前线程上下文运行用户注册的信号处理函数,然后再返回用户态,系统调用接口返回-1,并将错误码errno设置为EINTR。
2)不可被中断的系统调用。不打断当前的系统调用,等待系统调用自己返回,等到从内核态返回用户态的时候,先处理超时信号,在当前线程上下文运行用户注册的信号处理函数,然后再返回用户态执行之前的流程。如下图所示(图片源自网络)。
备注:什么是可被中断的系统调用和不可被中断的系统调用?内核是通过wait queue来实现系统调用的阻塞等待,如果调用的wait_event_interruptible系列函数,就是可被中断的系统调用;如果调用的是wait_event系列函数就是不可被中断的系统调用。
4. 信号的处理方式
忽略信号。大部分信号都可以被忽略,除了SIGSTOP和SIGKILL,这是系统或者Root用户杀掉进程的手段。
捕获信号,自定义处理函数。注册自定义的信号处理函数,除了SIGSTOP和SIGKILL。
系统默认处理。内核定义的默认动作,有5种情况:
a) 流产abort:终止进程并产生core文件。
b) 终止stop:终止进程但不生成core文件。
c) 忽略:忽略信号。
d) 挂起suspend:挂起进程。
e) 继续continue:若进程是挂起的,则resume进程,否则忽略此信号。
5. 内核对信号的管理机制
内核在每个进程的控制块中设计了一个Signal的位图信息,其中的每位与具体的Signal相对应,与中断机制是一致的。当系统中一个进程A通过系统调用向进程B发送Signal时,设置进程B的对应Signal位图,类似于触发了Signal对应中断,当进程B从内核态返回用户态时检查signal位图信息表是否有待处理的Signal。
对于实时信号,采用Queue管理已经挂起的重复信号。如下图所示(图片源自网络)。
6. 信号相关的系统调用
1)进程相关的接口
signal(),Linux早期的信号安装函数,系统调用signal用来设定某个信号的处理方法。
kill(),Linux早期的信号发送函数,向进程发送一个信号。
pause(),使调用的进程进入睡眠,直到接收到一个信号为止。
Alarm(),设置一个定时器,当定时器计时到达时,将发出一个信号给进程。
sigaction(),Linux新版本的信号安装函数。
sigqueue(),Linux新版本的信号发送函数,向进程发送一个信号。
注意,当一个信号被发送到一个多线程的进程中(注意是发送到进程),内核会选择该进程中的任意线程来处理该信号。
2)线程相关的接口
a)信号掩码的操作
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldest)
注意,当新线程创建后,它将继承创建它的线程的信号掩码。
b)发送信号到指定的线程
int pthread_kill(pthread_t thread, int sig)
int pthread_sigqueue(pthread_t thread, int sig, const union sigval value)
注意,pthread_sigqueue()函数是在glibc-2.11时加入的。它要求linux2.6.31,因为该版本提供了rt_tgsigqueueinfo()系统调用的。
7. 其它
1)Sigaction常用的Flag
SA_NODEFER:当进程执行一个信号处理函数时,系统默认屏蔽相应的信号,即自动阻塞这个信号,直到处理程序结束,因此,所处理的信号的另一次出现,并不能中断信号处理程序,所以信号处理函数不必是可以重入的。如果用户设置了SA_NODEFER则不屏蔽该信号,存在重入的场景,如,在信号处理函数中发送相同的信号。
SA_RESTAR:使被信号打断的系统调用自动重新发起,系统默认不会重启被打断的系统调用。
A_RESETHAND:等同于SA_ONESHOT,注册的信号处理函数是一次性的,执行过后该信号的处理函数就变成了默认处理函数SIG_DFL。
2)网络编程相关的信号
SIGPIPE:默认情况下,往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号,SIGPIPE默认行为是结束进程,用户需要修改这样的默认行为。
SIGURG:在linux环境下,内核通知应用程序带外数据到达主要有两种方法:一种是I/O复用技术,select等系统调用在接收到带外数据时将返回,并向应用程序报告socke上的异常事件,另一种方法就是SIGURG信号。