22

Linux系统编程—信号捕捉

 3 years ago
source link: https://segmentfault.com/a/1190000037509458
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前面我们学习了信号产生的几种方式,而对于信号的处理有如下几种方式:

  1. 默认处理方式;
  2. 忽略;
  3. 捕捉。

信号的捕捉,说白了就是抓到一个信号后,执行我们指定的函数,或者执行我们指定的动作。下面详细介绍两个信号捕捉操作参数: signalsigaction

signal函数

函数原型:

sighandler_t signal(int signum, sighandler_t handler);

其中,sighandler定义是这样的:typedef void (*sighandler_t)(int);

函数作用:

注册一个信号捕捉函数,也就是说,收到了某个信号,就执行它所注册的回调函数。

函数参数:

signum:信号编号,尽量用宏来写,而别用数字,这样更适合跨平台;

handler:注册的回调函数;

函数缺陷:

由于历史原因,该函数在不同版本的Unix和Linux系统中可能起到的效果不一样,所以跨平台性不佳,尽量避免使用它,取而代之使用通用性更好的sigaction函数。

#include <stdio.h>
 #include <signal.h>
 
 void func()
 {
     printf("SIGQUIT catched!\n");
 }
 
 int main()
{
    signal(SIGQUIT, func);
    while(1);
}

sigaction函数

函数原型:

int sigaction(int signum, const struct sigaction act, struct sigaction oldact);

函数作用:

与signal函数类似,用来注册一个信号捕捉函数;

返回值:

成功:0;失败:-1,并设置errno;

参数:

signum:信号编号,尽量用宏来写,而别用数字,这样更适合跨平台;

act:传入参数,新的信号捕捉方式;

oldact:传出参数,旧的信号捕捉方式

这里特别要注意参数中struct sigaction结构体,这也是这个函数的难点所在,下面详细说明:

struct 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_restorer和sa_sigaction这两个成员一个已经被弃用了,另一个很少使用,所以我们暂且不管它们,重点掌握剩下的三个。

sa_handler :指定信号捕捉后的处理函数,即注册回调函数。该成员也可以赋值为SIG_IGN,表示忽略该信号,也可注册为SIG_DFL,表示执行信号的默认动作。

sa_mask :临时阻塞信号集(或信号屏蔽字)先来看这样一个情景:

某个信号已经注册了回调函数,当内核传递这个信号过来时,会先经过一个阻塞信号集,先阻塞掉部分信号。再去执行对应的回调函数。如下图示:

IvyERvA.png!mobile

假如说,这个回调函数回调执行的时间比较长,比如2秒,在这2秒里,又有其它的信号过来,那进程是暂停当前回调函数,去响应新的信号,还是不管新来的信号,先把当前回调函数处理完再说?

正确的做法是,在执行回调函数期间,使用sa_mask临时的去替代进程的阻塞信号集,保证回调函数安心的执行完毕,再解除替代。注意:这个过程仅仅发生在回调函数执行期间,是临时性的设置。

sa_flags :通常设置为0,表示使用默认属性。

再来看另外一个场景:

比如进程对SIGQUIT注册了回调函数,当回调函数在执行期间,又来了SIGQUIT函数,这时,进程是响应还是不响应该信号?这就是sa_flags的一个作用,当其设置为0时,表示使用默认属性,也就是先不响应该信号,而是执行完回调函数再处理此信号。

另外,阻塞的常规信号不支持排队,也就是说,执行回调函数期间,再来千百个同个信号时,系统只记录一次。而后面的32个实时信号则支持排队。

#include <stdio.h>
 #include <signal.h>
 #include <unistd.h>
 
 void func(int signal)
 {
     printf("SIGQUIT catched!\n");
     sleep(2);   //用来模拟回调函数执行很长时间
     printf("func finished!\n");
}

int main()
{
    struct sigaction act;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);  //先清空临时阻塞信号集
    sigaddset(&act.sa_mask, SIGINT);    // 执行回调函数期间,屏蔽SIGINT
    act.sa_flags = 0;

    sigaction(SIGQUIT, &act, NULL); //注册回调函数

    while(1);

    return 0;
}

更多精彩内容,请关注公众号 良许Linux ,公众内回复 1024 可免费获得5T技术资料,包括: Linux,C/C++,Python,树莓派,嵌入式,Java,人工智能 ,等等。公众号内回复 进群 ,邀请您进高手如云技术交流群。

1460000024436106

最后,最近很多小伙伴找我要 Linux学习路线图 ,于是我根据自己的经验,利用业余时间熬夜肝了一个月,整理了一份电子书。无论你是面试还是自我提升,相信都会对你有帮助!

免费送给大家,只求大家金指给我点个赞!

电子书 | Linux开发学习路线图

也希望有小伙伴能加入我,把这份电子书做得更完美!

有收获?希望老铁们来个三连击,给更多的人看到这篇文章


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK