signal
Unix系统提供了两种方法来改变信号处置:
- signal()
- sigaction()
两者对比:
- signal()系统调用是设置信号处理的原始API,所提供的接口比sigaction()简单。
- sigaction()提供了 signal()所不具备的功能。
- signal()的行为在不同 UNIX 实现间存在差异,这也意味着对可移植性有所追求的程序绝不能使用此调用来建立信号处理器函数。
故此,sigaction()是建立信号处理器的首选 API(强力推荐)
NAME
signal - ANSI C signal handling
SYNOPSIS
#include <signal.h>
typedef void (*sighandler_t)(int);
/*
* 参数: 指定信号
* handler: SIG_IGN忽略该信号,SIG_DFL采用系统默认方式处理信号,自定义的信号处理函数指针。
*/
sighandler_t signal(int signum, sighandler_t handler);
DESCRIPTION
signal()的行为在UNIX版本之间有所不同,并且在历史上在Linux的不同版本中也有所不同。
避免使用它:改为使用sigaction(2)。 请参阅下面的可移植性。
signal() 将信号符号的设置设置为处理程序,该处理程序可以是SIG_IGN,SIG_DFL或程序
员定义的函数的地址("signal handler"[信号处理程序]).
如果信号信号传递到该过程,则发生以下情况之一 :
* 如果将处置设置为SIG_IGN,则忽略该信号 .
* 如果将处置设置为SIG_DFL,则发生与信号关联的默认操作 (see signal(7)) .
* 如果将处置设置为函数,则首先将处置重置为SIG_DFL,或者阻止信号(请参见下面的可移植性),
然后使用参数signum调用处理程序。 如果调用处理程序导致信号被阻塞,则从处理程序返回后,
信号将被解除阻塞。
信号SIGKILL和SIGSTOP不能被捕获或忽略。
RETURN VALUE
signal() 返回信号处理程序的先前值,如果出错则返回SIG_ERR。 如果发生错误,则设置errno来指示原因。
signal()向内核注册当前进程收到信号的处理方式:
- 第一个参数 sig,标识希望修改处置的信号编号
- 第二个参数 handler,目标进程在收到信号时,需要定义一个接收函数来处理(信号处理函数),handler则标识信号抵达时所调用函数的地址
- 返回值是之前的信号处置。换言之,编写如下代码,可以暂时为信号建立一个处理器函数,然后再将信号处置重置为其本来面目:
void (*oldhandler)(int);
oldhandler = signal(SIGINT, newhandler);
if(oldHandler == SIG_ERR){
perror("signal");
exit(1);
}
// ......
if(signal(SIGINT, oldHandler ) == SIG_ERROR){
perror("signal");
exit(1);
}
信号处理函数的原型如下:
#include <signal.h>
typedef void (*sighandler_t)(int);
信号处理函数只带有一个整型参数,该参数用来指示信号类型。信号处理函数应该是可重入的。
处理用户自定义信号处理函数之外,bits/signum.h
头文件中还定义了信号的其他处理方式:SIG_IGN
(忽略信号), SIG_DEL
(使用信号的默认处理方式)
#include <bits/signum.h>
#include SIG_DFL((__sighandler_t) 0)
#include SIG_IGN((__sighandler_t) 1)
也就是说,在为 signal()指定 handler 参数时,可以以SIG_IGN
、 SIG_DEL
(这适用于将之前 signal()调用所改变的信号处置还原)来代替函数地址
调用 signal()成功将返回先前的信号处置,有可能是先前安装的处理器函数地址,也可能
是常量 SIG_DFL 和 SIG_IGN 之一。如果调用失败,signal()将返回 SIG_ERR。
实例
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
void signalHandler( int signum )
{
cout << "Interrupt signal (" << signum << ") received.\n";
// 清理并关闭
// 终止程序
exit(signum);
}
int main ()
{
int i = 0;
// 注册信号 SIGINT 和信号处理程序
if ( signal(SIGINT, signalHandler) == SIG_ERR) {
puts("can not catch SIGINT");
exit(1);
}
while(++i){
cout << "Going to sleep...." << endl;
if( i == 3 ){
raise( SIGINT);
}
sleep(1);
}
return 0;
}
分析:第3s之后给自己发送一个SIGINT信号,由于之前使用signal注册了一个信号处理函数signalHandler。因此,一收到SIGINT信号,就会调用signalHandler函数,而signalHandler函数的处理方法是打印并退出。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int signo) {
if (signo == SIGINT) {
puts("received SIGINT");
}
}
int main() {
if (signal(SIGINT, sig_handler) == SIG_ERR) {
puts("can not catch SIGINT");
}
while (1) {
sleep(1);
}
return 0;
}
分析:前台程序可以通过键入ctrl+c发送一个SIGINT信号,收到信号之后,会调用sig_handler函数,sig_handler函数的动作是打印"received SIGINT"。然后通知ctrl+z发送另一个信号,这个信号的默认动作是杀死进程
常见写法:
//程序退出标记
bool g_bExitFlag = false;
int main(int argc,char *argv[]){
//设置程序退出信号处理函数
signal(SIGINT, [](int){g_bExitFlag = true;});
while (!g_bExitFlag){
printf(" do something ...... \n");
sleep(1);
}
return 0;
}
信号安装器
信号处理器程序(也称为信号捕捉器)是当指定信号传递给进程时将会调用的一个函数。调用信号处理器程序,可能会随时打断主程序流程;内核代表进程来调用处理器程序,当处理器返回时,主程序会在处理器打断的位置恢复执行:
虽然信号处理器程序几乎可以为所欲为,但一般而言,设计应力求简单
- 信号处理函数
内核在调用信号处理器程序时,会将引发调用的信号编号作为一个整型参数传递给处理
器函数。如果信号处理器程序只捕获一种类型的信号,那么这个参数几乎无用。然而,如果安装相同的处理器来捕获不同类型的信号,那么就可以利用此参数来判定引发对处理器调用的是何种信号
// 为两个不同信号建立同一处理器函数
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int num){
if(num == SIGINT){
printf("receive the SIGINT: %d\n", num);
}
else if (num == SIGQUIT){
printf("recevie the SIGQUIT: %d\n", num);
}
}
int main()
{
if(signal(SIGINT, sig_handler) == SIG_ERR){
perror("siggna");
exit(EXIT_FAILURE);
}
if(signal(SIGQUIT, sig_handler) == SIG_ERR){
perror("siggna");
exit(EXIT_FAILURE);
}
printf("enter to the while.\n");
while(1){
sleep(1);
}
exit(0);
}
按下Ctrl+c发出中断信号,也就是发出SIGINT信号;按下Ctirl+\发出退出信号,也就是发出SIGQUIT信号;
signal函数是让程序捕获到设置的信号(第一个参数指定)的时候,去执行设置的信号处理函数(第二个参数指定);
运行结果如下:
如果想退出程序,按下Ctrl+z强制退出,但是此时输入ps,可以看到刚才运行的程序没有真正退出,这时候我们要杀死该进程;
输入:kill -9 进程号 然后再ps查看,可以看到刚才的进程已经被杀死;
- 通过异步方式,给子进程收尸
注意:子进程在终止时会给父进程发SIGCHLD,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需要专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void child_exit_handler(int signum)
{
if(signum == SIGCHLD)
{
printf("Child exit.\n");
wait(NULL);
}
}
int main()
{
int pid;
int i = 0;
//想内核注册,处理 SIGCHLD信号的方式
if( signal(SIGCHLD,child_exit_handler) == SIG_ERR){
perror("signal");
exit(EXIT_FAILURE);
}
if((pid = fork()) < 0)
{
perror("Fail to fork");
exit(EXIT_FAILURE);
}else if(pid == 0){
for(i = 0;i < 5;i ++)
{
printf("child loop.\n");
sleep(1);
}
}else{
for(i = 0;i < 5;i ++)
{
printf("Father loop.\n");
sleep(2);
}
}
exit(EXIT_SUCCESS);
}