Linux系统编程—信号捕捉

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

  1. 默认处理方式;
  2. 忽略;
  3. 捕捉 。
信号的捕捉 , 说白了就是抓到一个信号后 , 执行我们指定的函数 , 或者执行我们指定的动作 。 下面详细介绍两个信号捕捉操作参数:signal和sigaction 。
##signal函数
函数原型:
sighandler_t signal(int signum, sighandler_t handler);
其中 , sighandler定义是这样的:typedef void (*sighandler_t)(int);
函数作用:
注册一个信号捕捉函数 , 也就是说 , 收到了某个信号 , 就执行它所注册的回调函数 。
函数参数:
signum:信号编号 , 尽量用宏来写 , 而别用数字 , 这样更适合跨平台;
handler:注册的回调函数;
函数缺陷:
由于历史原因 , 该函数在不同版本的Unix和Linux系统中可能起到的效果不一样 , 所以跨平台性不佳 , 尽量避免使用它 , 取而代之使用通用性更好的sigaction函数 。
#include#includevoid 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);
};
【Linux系统编程—信号捕捉】这个结构体成员很多 , 又很多是回调函数的形式 , 令人望而生畏 。 但实际上 , 需要掌握的只有三个 。
首先 , sa_restorer和sa_sigaction这两个成员一个已经被弃用了 , 另一个很少使用 , 所以我们暂且不管它们 , 重点掌握剩下的三个 。
① sa_handler:指定信号捕捉后的处理函数 , 即注册回调函数 。 该成员也可以赋值为SIG_IGN , 表示忽略该信号 , 也可注册为SIG_DFL , 表示执行信号的默认动作 。
② sa_mask:临时阻塞信号集(或信号屏蔽字)先来看这样一个情景:
某个信号已经注册了回调函数 , 当内核传递这个信号过来时 , 会先经过一个阻塞信号集 , 先阻塞掉部分信号 。 再去执行对应的回调函数 。 如下图示:
Linux系统编程—信号捕捉文章插图
假如说 , 这个回调函数回调执行的时间比较长 , 比如2秒 , 在这2秒里 , 又有其它的信号过来 , 那进程是暂停当前回调函数 , 去响应新的信号 , 还是不管新来的信号 , 先把当前回调函数处理完再说?
正确的做法是 , 在执行回调函数期间 , 使用sa_mask临时的去替代进程的阻塞信号集 , 保证回调函数安心的执行完毕 , 再解除替代 。 注意:这个过程仅仅发生在回调函数执行期间 , 是临时性的设置 。
③ sa_flags:通常设置为0 , 表示使用默认属性 。
再来看另外一个场景:
比如进程对SIGQUIT注册了回调函数 , 当回调函数在执行期间 , 又来了SIGQUIT函数 , 这时 , 进程是响应还是不响应该信号?这就是sa_flags的一个作用 , 当其设置为0时 , 表示使用默认属性 , 也就是先不响应该信号 , 而是执行完回调函数再处理此信号 。