文章目录
- 前言
- 一、sigaction
- __sighandler_t sa_handler;
- __sigset_t sa_mask;
- 二、volatile关键字
- 三、SIGCHLD
- 方法一
- 方法二
前言
本章内容主要对之前的内容做一些补充。
一、sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
之前我们学过signal来对信号进行捕捉,sigaction也是一个对信号进行捕捉的系统接口函数,不过sigaction要相对复杂一些。
参数 int signum 是要捕捉的信号编号。
参数struct* sigaction 在这里作为输入型参数,是提供给我们的一个结构体指针类型,这里的结构体名和函数名相同。
参数struct sigaction *oldact 在这里作为输出型参数。
那么struct sigaction 里面有什么呢?
struct sigaction{/* Signal handler. */
#ifdef __USE_POSIX199309union{/* Used if SA_SIGINFO is not set. */__sighandler_t sa_handler;/* Used if SA_SIGINFO is set. */void (*sa_sigaction) (int, siginfo_t *, void *);}__sigaction_handler;
# define sa_handler __sigaction_handler.sa_handler
# define sa_sigaction __sigaction_handler.sa_sigaction
#else__sighandler_t sa_handler;
#endif/* Additional set of signals to be blocked. */__sigset_t sa_mask;/* Special flags. */int sa_flags;/* Restore handler. */void (*sa_restorer) (void);};
我们今天主要对函数体内部的sa_handler和sa_mask进行讨论
__sighandler_t sa_handler;
typedef void __signalfn_t(int);
typedef __signalfn_t *__sighandler_t;
根据__sighandler_t的定义,我们可以知道其本质是一个函数指针,所以这里的我们就可以知道其实本质也是像signal一样使用回调函数来进行信号的捕捉。
__sigset_t sa_mask;
typedef __sigset_t sigset_t;
之前我们在学习sigprocmask和sigaddset等信号集接口函数的时候有过接触sigset_t,那么这里的sa_mask是什么呢?
先提出一个观点,在一个信号被处理(递达)过程中,如果同一个信号再次被发送且进入pending表,那么OS是怎样处理的? OS的处理方式是block(阻塞)相同信号,不再重复递达,等到处理完正在被处理的信号再根据情况决定。 而sa_mask在这里的作用就是可以根据其信号集的有效信号,在signum信号正在被处理时,同时阻塞sa_mask的有效信号和其本身信号。
示例代码如下
#include<iostream>
#include<cstdio>
#include<signal.h>
#include<unistd.h>void ShowPending()
{sigset_t pending;sigemptyset(&pending);for (int i = 1; i <= 31; i++){sigpending(&pending);// 通过sigismember来打印我们的pending信号集std::cout << sigismember(&pending, i);}std::cout << std::endl;
}void catchSig(int signum)
{std::cout << "捕捉到" << signum << "信号!" << std::endl; int count = 0;while(1){ShowPending();count++;if(count == 50) break;sleep(5);}
}
int main()
{std::cout << "pid: " << getpid() << std::endl;//1.定义struct sigaction类型struct sigaction act , oldact;//2.mask信号集初始化sigset_t mask;sigemptyset(&mask);//3.mask信号集添加1号,2号,3号, 4号,5号,6号作为有效信号sigaddset(&mask,1);sigaddset(&mask,2);sigaddset(&mask,3);sigaddset(&mask,4);sigaddset(&mask,5);sigaddset(&mask,6);//4.修改act中的数据act.sa_handler = catchSig;act.sa_mask = mask;//5.调用sigactionsigaction(2, &act , &oldact);while(1) sleep(1);return 0;
}
运行结果
二、volatile关键字
我们之前的学习过程中,也提到过编译器会进行优化,例如我们之前讲的拷贝构造和右值引用都有提到过,而volatile主要解决关于编译器优化所导致的问题。
是的,编译器优化在少数情况下是会造成一些问题的。
而Linux中的gcc编译器是有几种不同程度的优化方案的
-O -O0 -O1 -O2 -O3 -Os -Ofast -Og
在使用gcc或g++命令时,上面的选项从左到右,编译时优化程度依次变大。
示例代码如下
#include<iostream>
#include<cstdio>
#include<signal.h>
#include<unistd.h>int flag = 0;void catchSig(int signum)
{std:: cout << flag ;flag = 1;std::cout << "->" << flag <<std::endl;
}int main()
{signal(2, catchSig);while(1){if(flag == 1) break;;}std::cout << "程序正常退出" << std::endl;return 0;
}
这段代码如果使用
g++ -o mysignal mysignal.cc -std=c++11
进行编译
结果则是
这段代码如果使用
g++ -o mysignal mysignal.cc -std=c++11 -O3
进行编译
结果则是
程序不会自动退出。
这是因为在-O3的优化程度下,编译器检测默认执行流不会修改flag的数据,所以这里的cpu寄存器一直储存着原有的flag值0,导致在判断flag时,一直使用寄存器中的0在判断,导致循环无法退出。
现在我们使用volatile来试试
#include<iostream>
#include<cstdio>
#include<signal.h>
#include<unistd.h>volatile int flag = 0;void catchSig(int signum)
{std:: cout << flag ;flag = 1;std::cout << "->" << flag <<std::endl;
}int main()
{signal(2, catchSig);while(1){if(flag == 1) break;;}std::cout << "程序正常退出" << std::endl;return 0;
}
这个时候程序就正常推出了,所以这里volatile的意思就是让告诉编译器不要对flag进行优化,要让寄存器看到内存中的数据!
三、SIGCHLD
SIGCHLD 在子进程停止或者退出时可能收到。
所以我们再学习几种进程等待的方法。
方法一
#include <iostream>
#include <cstdio>
#include <signal.h>
#include <unistd.h>int main()
{signal(SIGCHLD, SIG_IGN);pid_t id = fork();if(id == 0){//子进程sleep(10);exit(0);}//父进程while(1);return 0;
}
将SIGCHLD信号的处理方式变为忽略。
子进程没有僵尸,而是成功回收。
方法二
#include <iostream>
#include <cstdio>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
void catchCHLD(int signum)
{std::cout << "捕捉到SIGCHLD信号!" << std::endl;int pid = 0;while((pid = waitpid(0,nullptr,WNOHANG)) > 0){std::cout << "成功等待" << pid << "号进程" << std::endl;}
}int main()
{signal(SIGCHLD, catchCHLD);pid_t id = fork();if(id == 0){//子进程sleep(10);exit(0);}//父进程while(1);return 0;
}
也同样可以成功回收!