linux之信号处理函数signal和sigaction

时间:2021-11-11 23:35:12

signal回调函数绑定

信号(signal)是一种软件中断,它提供了一种处理异步事件的方法,也是进程间惟一的异步通信方式。那么我们很多场景下,需要对不同的信号进行捕获并做出响应操作。
在linux上,我们有两种方式完成该操作:

1. signal

#include <signal.h>
signal(int, void (*)(int));

第一个参数是我们要捕获的信号类型,第二个参数是我们要绑定的信号处理函数。

2. sigaction

#include <signal.h>
int sigaction(int, const struct sigaction * __restrict, struct sigaction * __orestrict);

相比较signal来说,sigaction与它的功能是类似的,但是更全面。
先说一下参数吧,第一个参数同signal,后两个参数类型是一个名为sigaction的结构体,该结构体表示对信号的处理方式,__restrict为新设定的处理方式,__orestrict参数则用于获取之前的信号处理方式。
我们来看一下这个结构体的内容:

#include <signal.h>
struct sigaction {
union __sigaction_u __sigaction_u; /* signal handler */
sigset_t sa_mask; /* signal mask to apply */
int sa_flags; /* see signal options below */
};

union __sigaction_u {
void (*__sa_handler)(int);
void (*__sa_sigaction)(int, siginfo_t *,
void *);
};

可以看到该结构体主要由三部分构成,

sa_mask,一个信号集
sa_flags, 设置程序收到信号时的行为
signaction_u 表示信号处理函数

sa_mask和sa_flags都是用二进制位的形式来保存信息的,前者代表着当前进程所能接收到的信号集合,后者代表信号处理的设置信息。
sa_flags的值有好多,这里我们只说一个常用的SA_RESTART,书上的解释是【重新调用被该信号终止的系统调用】,最开始我没有理解,写了个小例子尝试后,清晰了许多。
一起看看吧

void action(int a)
{
printf("%d -=-=\n", a);
}

int main()
{
int pid = fork();
if(pid > 0)
{
struct sigaction s;
memset(&s, 0, sizeof(s));
s.sa_handler = action;
sigfillset(&s.sa_mask);
//s.sa_flags |= SA_RESTART;
assert(sigaction(SIGCHLD, &s, NULL) != 1);
}
else if(pid == 0)
{
sleep(2);
exit(0);
}

char str[10];
scanf("%s", str);
printf("%s\n", str);

puts("End");

return 0;
}

上面这段代码是对SIGCHLD信号(当子进程状态发生变化时产生)进行了信号处理的设定。
当子进程执行exit后,会给父进程发送SIGCHLD信号,而父进程本来此时正在进行scanf函数,等待用户输入,在接受到SIGCHLD信号之后,中断目前正在进行的系统调用,然后去执行信号处理函数,也就是上述代码中设定的action函数。在执行完action之后,程序会继续往下执行,也就是下面的结果
linux之信号处理函数signal和sigaction

但是当我们把
s.sa_flags |= SA_RESTART;
这句话注释去掉之后再进行运行,结果如下:
linux之信号处理函数signal和sigaction
为什么的,因为SA_RESTART参数的设置,信号处理函数在执行完之后会对之前信号中断的系统调用进行重新调用,比如我们这里使用的scanf函数。