Signal, Linux进程间通信
信号(Signal)是Linux的一种进程间通信机制,信号由内核或者其他进程发送给接收进程,接收到信号的进程相应地采取行动来处理信号。信号是一种软中断,即当一个进程收到一个信号时,它停止自己的执行流程,转而去处理信号。
值得注意的是信号是可重入的,即信号处理程序可以被其他信号中断。
信号的种类
常用的信号并不是很多,信号的名称在<signal.h>
中定义,常用的信号及解释如下:
- SIGALRM:由
alarm()
系统调用发送给接收进程,alarm()
系统调用可以定时发送一个SIGALRM信号给进程,以触发某个定时操作。 - SIGHUP:该信号表示某一个操作关闭了终端,运行在该终端下的程序会收到SIGHUP信号,默认的信号处理程序会结束收到的信号的程序。
- SIGINT:用户输入了Ctrl-C就会发送一个SIGINT给前台应用。
- SIGILL:这是一个异常信号,表示系统执行时遇到了一个非法程序,当加载动态链接一个被破坏的函数库时可能会产生该信号。注意,这个信号不能被捕获或者忽略,一般在shell中用于强制终止异常程序。
- SIGABRT:程序中调用
abort()
系统调用会产生一个SIGABRT信号 - SIGSEGV:这也是一个异常信号,当程序访问了一个不属于它的内存空间是内核会发送一个SIGSEGV信号给程序。
- SIGPIPE:Broken Pipe,如果程序尝试往管道写入数据,而管道并没有与之对应的读数据的进程,就会产生SIGPIPE信号。
- SIGTREM:这个信号告诉进程结束自身的运行,
kill
命令默认发送SIGTERM信号 - SIGHLD:当一个子进程结束运行时,父进程就会收到一个SIGHLD信号。
- SIGUSR1 & SIGUSR2:用户可自定义的信号。
注意SIGILL
和SIGSTOP
(用于调试)的处理行为不能被更改。
POSIX信号的语义
符合POSIX的系统上信号的处理总结为:
- 一旦安装了信号处理函数,它就一直安装着
- 在一个信号处理函数运行期间,正被递交的信号是阻塞的。而且安装处理函数时,POSIX保证传递给
sigaction
的sa_mask
信号集中指定的任何信号都是被阻塞的。 - 如果一个信号在被阻塞期间产生了一次或多次,那么该信号在被解阻塞之后通常只递交一次,也就是说,Unix信号默认是不排队的。
- 利用sigprocmask函数选择性地阻塞或解阻塞一组信号是可能的,我们可以在一段临界代码执行期间,防止捕获某些信号,以此保护这段代码。
信号的处理
signal函数
signal
函数是一个年代久远的信号处理函数,其函数原型如下:
1 | void (*signal(int, void (*handler)(int)))(int); |
可以看到,signal是一个函数,其返回值是一个函数指针,signal函数注册一个新的信号处理函数,并返回原先注册的信号处理函数。
一个signal
函数的例子如下:
1 | #include <stdio.h> |
结果截图:
可以看到当我们按下Ctrl-C
时触发了自定义的信号处理函数。
sigaction函数
signal
函数的行为根据UNIX版本的不同而不同,在不同的Linux版本中亦是如此。所以我们应该使用一个更加健壮的接口--sigaction函数。sigaction的原型为:
1 | #include <signal.h> |
sigaction
的结构定义如下:
1 | struct sigaction { |
sa_flags
被设置成SA_SIGINFO
时,信号处理函数是sa_sigaction
函数,否则是sa_handler
函数。sa_flags
的其他值的解释如下:
- SA_NOCLDSTOP:子进程停止时不产生SIGHLD信号
- SA_RESETHAND:将对此信号的处理方式在信号处理函数入口处重置为SIG_DFL
- SA_RESTART:重启可中断的函数而不是给出EINTER错误
- SA_NODEFER:捕获到信号时不将它添加到屏蔽字中。
当我们使用sa_sigaction
函数作为信号处理函数时,我们可以得到发送信号的进程的更多信息,信息存储在siginfo_t
中,其结构如下:
1 | siginfo_t { |
sa_mask
中设置被进程屏蔽的信号。
一个使用sigaction
的例子:
1 | #include <stdio.h> |
结果如下:
可以看到,程序第一次收到SIGINT信号时执行的是自定义的信号处理函数,第二次就被重置为默认的信号处理函数了。
kill函数
kill
系统调用函数的原型如下:
1 | #include <sys/types.h> |
它的作用是发送一个信号(由sig参数指定)给一个特定进程。默认发送SIGTERM
信号。
信号集处理函数
信号集处理函数主要用于处理sigset_t
,从而让进程屏蔽某些信号。函数的原型如下:
1 | #include <signal.h> |
sigpromask
可以根据how
指定的值来修改进程的信号屏蔽字。如果set
参数为空,则how
没有意义,但是如果此时osigset
不为空,那么当前信号屏蔽字保存在osigset
指向的地址中。
how
的不同取值和对应的操作:
- SIG_BLOCK:把参数set中的信号加入到进程的信号屏蔽字中
- SIG_SETMASK:把进程的信号屏蔽字设置为set中的信号
- SIG_UNBLOCK:从进程的信号屏蔽字中删除set中指定的信号
注意,调用这个函数才能修改进程的信号屏蔽字,它之前的函数都只是改变一个变量,对进程的信号屏蔽字并没有直接影响。
sigpending
函数将被阻塞的信号中停留在待处理状态的信号写入参数指定的地址中。
sigsuspend
函数首先将进程的屏蔽字换成参数指定的信号,然后挂起进程。
一个例子实验一下以上的函数:
1 | #include <unistd.h> |
运行结果:
可以看到当我们屏蔽了SIGINT信号后,如果SIGINT发送给进程,那么信号就被阻塞编程待处理状态,当我们取消对信号的阻塞后该信号马上就被处理,于是程序就马上打印
1 | The app will exit in 5 seconds! |