目录
一、什么是信号
信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动。比如kill、程序异常crash、段错误等。
但它们还可以作为进程间通信或修改行为的一种方式,明确地由一个进程发送给另一个进程。一个信号的产生叫生成,接收到一个信号叫捕获。
二、信号的种类
信号的名称是在头文件signal.h中定义的,信号都以SIG开头,常用的信号并不多,常用的信号如下:
更多的信号类型可在kernel目录下搜signal.h
三、信号的处理 —— signal()函数
程序可用使用signal()函数来处理指定的信号,主要通过忽略和恢复其默认行为来工作。signal()函数的原型如下:
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
这是一个相当复杂的声明,耐心点看可以知道signal是一个带有sig和func两个参数的函数,func是一个类型为void (*)(int)的函数指针。
该函数返回一个与func相同类型的指针,指向先前指定信号处理函数的函数指针。准备捕获的信号的参数由sig给出,接收到的指定
信号后要调用的函数由参数func给出。其实这个函数的使用是相当简单的,通过下面的例子就可以知道。注意信号处理函数的原型
必须为void func(int),或者是下面的特殊值:
SIG_IGN : 忽略信号
SIG_DFL : 恢复信号的默认行为
说了这么多,还是给出一个例子来说明一下吧,源文件名signal.c,代码如下:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
printf("\nOUCH! - I got signal %d\n", sig);
// 恢复终端中断信号SIGINT的默认行为
(void) signal(SIGINT, SIG_DFL);
}
int main()
{
// 改变终端中断信号SIGINT的默认行为,使之执行ouch函数
// 而不是终止程序的执行
(void) signal(SIGINT, ouch);
while(1)
{
printf("Hello World!\n");
sleep(1);
}
return 0;
}
运行结果如下:
可以看到,第一次按下终止命令(ctrl+c)时,进程并没有被终止,面是输出OUCH! - I got signal 2,因为SIGINT的默认行为被signal()函数改变了,
当进程接受到信号SIGINT时,它就去调用函数ouch去处理,注意ouch函数把信号SIGINT的处理方式改变成默认的方式,所以当你再按一次ctrl+c时,
进程就像之前那样被终止了。
四、信号处理 —— sigaction()函数(扑获信号)
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:
1. 用户程序注册了SIGQUIT信号的处理函数sighandler。
2. 当前正在执行main函数,这时发生中断或异常切换到内核态。
3. 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
4. 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,
它们之间不存在调用和被调用的关系,是两个独立的控制流程。
前面我们看到了signal()函数对信号的处理,但是一般情况下我们可以使用一个更加健壮的信号接口 —— sigaction()函数。它的原型为:
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回-1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oldact指针非空,则通过oldact传出该信号原来的处理动作。act和oldact指向sigaction结构体:
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_handler:信号处理器函数的地址,亦或是常量SIG_IGN、SIG_DFL之一。仅当sa_handler是信号处理程序的地址时,亦即sa_handler的
- 取值在SIG_IGN和SIG_DFL之外,才会对sa_mask和sa_flags字段加以处理。
- sa_sigaction:如果设置了SA_SIGINFO标志位,则会使用sa_sigaction处理函数,否则使用sa_handler处理函数。
- sa_mask:定义一组信号,在调用由sa_handler所定义的处理器程序时将阻塞该组信号,不允许它们中断此处理器程序的执行。
- sa_flags:位掩码,指定用于控制信号处理过程的各种选项。
- SA_NODEFER:捕获该信号时,不会在执行处理器程序时将该信号自动添加到进程掩码中。
- SA_ONSTACK:针对此信号调用处理器函数时,使用了由sigaltstack()安装的备选栈。
- SA_RESETHAND:当捕获该信号时,会在调用处理器函数之前将信号处置重置为默认值(即SIG_IGN)。
- SA_SIGINFO:调用信号处理器程序时携带了额外参数,其中提供了关于信号的深入信息
将sa_handler/sa_sigaction赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号
的编号,这样就可以用同一个函数处理多种信号。这是个回调函数,不是被main函数调用,而是被系统所调用。
注意 : sa_restorer 参数已废弃不用。
sa_handler主要用于不可靠信号(实时信号当然也可以,只是不能带信息),sa_sigaction用于实时信号,可以带信息(siginfo_t),两者不能同时出现。sa_flags有几个选项,比较重要的有两个:SA_NODEFER 和 SA_SIGINFO,当SA_NODEFER设置时在信号处理函数执行期间不会屏蔽当前信号;当SA_SIGINFO设置时与sa_sigaction 搭配出现,sa_sigaction函数的第一个参数与sa_handler一样表示当前信号的编号,
第二个参数是一个siginfo_t 结构体,第三个参数一般不用。当使用sa_handler时sa_flags设置为0即可。
五、扑获信号示例
第一种:当信号来时,catch 信号,调用自己的处理函数后信号结束。
实例1:
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while( 0)
void handler( int sig);
int main( int argc, char *argv[])
{
struct sigaction act;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (sigaction(SIGINT, &act, NULL) < 0)
ERR_EXIT( "sigaction error");
for (; ;)
pause();
return 0;
}
void handler( int sig)
{
printf( "rev sig=%d\n", sig);
}
运行结果:
XXXXXX$ ./main (执行./main后,ctrl+c产生信号)
^Crev sig=2
^Crev sig=2
^Crev sig=2
按下ctrl+c 会一直产生信号而被处理打印recv语句。
示例2:
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while( 0)
void handler( int sig);
int main( int argc, char *argv[])
{
struct sigaction act;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
// 在信号处理函数执行期间屏蔽SIGQUIT信号,完毕后会抵达
sigaddset(&act.sa_mask, SIGQUIT);
/* 注意sigprocmask中屏蔽的信号是一直不能抵达的,除非解除了阻塞*/
act.sa_flags = 0;
if (sigaction(SIGINT, &act, NULL) < 0)
ERR_EXIT( "sigaction error");
for (; ;)
pause();
return 0;
}
void handler( int sig)
{
printf( "rev sig=%d\n", sig);
sleep( 5);
}
先按下ctrl+c ,然后马上ctrl+\,程序是不会马上终止的,即等到handler处理完毕SIGQUIT信号才会抵达。
xxx$ ./sa_mask
^Crev sig=2
^\
5s过后接着才输出Quit (core dumped),即在信号处理函数执行期间sa_mask集合中的信号被阻塞直到运行完毕。
示例3:
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while( 0)
void handler( int, siginfo_t *, void *);
int main( int argc, char *argv[])
{
struct sigaction act;
//sa_sigaction与sa_handler只能取其一 ,sa_sigaction多用于实时信号,可以保存信息
act.sa_sigaction = handler;
sigemptyset(&act.sa_mask);
// 设置标志位后可以接收其他进程
// 发送的数据,保存在siginfo_t结构体中
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGINT, &act, NULL) < 0)
ERR_EXIT( "sigaction error");
for (; ;)
pause();
return 0;
}
void handler( int sig, siginfo_t *info, void *ctx)
{
printf( "recv a sig=%d data=%d data=%d\n",
sig, info->si_value.sival_int, info->si_int);
}
第二种:当信号来时,catch 信号,调用自己的处理函数,执行完之后,继续调用信号默认的处理。
比如:当程序crash时,catch到abort信号,释放资源,然后继续abort的操作。这种方式很有用,如下图
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while( 0)
void signalHandler(int sig, siginfo_t* info, void* func);
static struct sigaction gOrigSigactionInt; // old的处理。
void signalHandler(int sig, siginfo_t* info, void* func)
{
printf("signal num : %d\n", sig);
if(sig==SIGINT)
gOrigSigactionInt.sa_sigaction(sig, info, func); //继续信号的默认处理
printf("end defalut...\n"); // 程序已经退出,不会打印
}
int main( int argc, char *argv[])
{
printf("start main..\n");
struct sigaction act;
memset(&act,0,sizeof(sigaction));
act.sa_flags = SA_SIGINFO|SA_ONSTACK;
sigemptyset(&act.sa_mask);
act.sa_sigaction = signalHandler;
if (sigaction(SIGINT, &act, &gOrigSigactionInt) < 0)
printf( "sigaction error");
for (; ;)
pause();
return 0;
}
输出结果:
运行xx$./main 。后执行ctrl+c 程序退出,输出如上。
参考:
linux系统编程之信号(四):信号的捕捉与sigaction函数_Meditation-CSDN博客
Linux进程间通信(一): 信号 signal()、sigaction() - 52php - 博客园
------------------------------------------华丽的分界线---------------------------------------------
如果哪位兄弟准备找工作的,可以找我,有很多大厂岗位机会。待遇不错喔。+_+