目录
sigaction
这里面第一个参数signum就类似于signal里面的signum, 都是用来重新设置某个信号的处理动作。 第二个参数我们会发现它的类型名称和我们的函数名称是一样的, 第三个参数也是这个类型。 那么这里的第二个参数其实是一个输入型参数。 第三个参数是一个输出型参数。其中第二个参数就是传送我们自己定义的自定义捕捉方法。 第三个参数就是将对应的信号的老的处理方法给我们传出来。
然后我们看一下sigaction类型的定义:
上面就是sigaction的定义, 但是里面有许多成员都是和实时信号相关的, 我们不关心。 我们只关心第一个和第三个成员变量。 其中第一个就是我们将来捕捉信号要执行的处理方法。
信号递达后pending位图清零的时机
信号处理时的屏蔽
sa_mask
另外sigaction里面还有一个成员变量交sa_mask。 这个sa_mask是sigset_t类型, 如哦正在处理2号信号, 那么2号信号就会自动被系统屏蔽。 如果我还想屏蔽更多信号呢? 也就是说, 我们在捕捉2号期间, 如果sa_mask默认, 那么就只会屏蔽2号信号。 但是如果我们想要屏蔽更多的信号, 就要用sa_mask进行设置。
函数重入
假如我们有下面的节点定义, 头插函数和自定义捕捉动作
现在我们有下面这个链表
现在我们执行insert。
此时的链表结构是这样的:
因为信号捕捉又是插入一个新节点:
所以, 就会再次进入insert函数插入新节点。那么此时的链表节点就是这样的:
最后, 信号捕捉完毕后, 再将原本的insert函数执行完, 就变成了最终结果, 如下:
上面有两个问题, 这两个问题合起来就是, 函数重入导致的节点丢失问题。 而我们把这种重入后会有问题的函数称为不可重入函数。即: 如果一个函数被重复进入的情况下, 出错了, 或者可能出错, 我们就把他叫做不可重入函数。 否则叫做可重入函数。 可不可重入是特点, 不含褒贬。 目前我们学过的大部分函数, 都是不可重入的。
volatile关键字
volatile在信号的角度怎么理解呢? 这里我们通过一串代码来进行理解。 首先写下下面的代码:
int flag = 0;
int main()
{
while (!flag);
cout << "process quit success" << endl;
return 0;
}
对于这串代码, 这串代码正常情况下是不能退出的, 会一直死循环。但是我们这里重新定义一下2号信号的处理方法:
int flag = 0;
void handler(int signo)
{
cout << "catch a signal: " << signo << endl;
flag = 1;
}
int main()
{
signal(2, handler);
while (!flag);
cout << "process quit success" << endl;
return 0;
}
这样, 如果我们捕捉2号后, flag被置为1. 那么就应该退出循环, 然后打印程序退出成功。 那么真实情况下是这样吗?
确实是我们预测的这样。 但是, 在极端情况下, handler和main是属于不同的执行流的。 编译器在编译的过程中会发现在main函数中, 没有任何地方去修改flag, 因为我们的while循环只是在对flag做检测, 并没有任何地方去修改。 所以编译器就可能会对flag做优化, 在优化条件下, flag变量就有可能被直接优化到cpu的寄存器当中, 为什么? 因为我们的flag在一个执行流中没有被修改; 而且, 我们也知道, while循环本身就能进行判断, 这种判断是一种计算, 计算本身都是在cpu中进行的。 而且cpu只会进行两种计算, 一种叫做算数计算, 一种叫做逻辑计算。 而这里的while判断就是逻辑运算, 所以就必须把flag放到cpu中进行计算。 那么flag既不会被修改, 又会到cpu中进行逻辑运算。那么flag就有可能会被优化到寄存器当中。
那么我们如何控制它进行优化呢?那么我们打开man g++, 然后/-O, 就能看到下面这个:
有-O1, -O2等等。 这里面的123是优化级别。 -O就是控制优化等级。
这里我们直接控制优化等级3, 观察效果:
我们就会发现程序退出不了了。 为什么呢? 正常来说, 我们的2号信号被捕捉后, flag置为1. 逻辑反就是0, 那么程序就应该被退出。 为什么没有退出呢?
那么我们看下面这张图:
左边是cpu, 右边是物理内存。 我们需要知道的是, 不管怎么优化, 我们对应的变量, 一定会在内存里面开辟。 我们一开始cpu做检测, 就是将内存里面的变量加载到寄存器, 然后做逻辑检测, 检测之后有结果了再控制逻辑。 但是现在不做这个工作了, 只是在第一次的时候将flag直接放到寄存器里, 然后cpu从寄存器里面直接计算。 也就是说,我们的cpu计算flag不再从内存里面拿了, 而是先保存到寄存器中,以后做检测直接从寄存器里面拿。 问题是这样对于flag如果不做修改会有用, 但是我们的flag会做修改。 flag后续修改后cpu并不会检测到, 所以我们的程序就一直死循环了。
SIGCHLD
子进程退出并不是单纯的静悄悄的退出, 当它退出的时候, 它会向父进程发送信号, 这个信号就是SIGCHLD(17号。 怎么证明会发送这个信号呢? ——是不是我们父进程对17号信号进行捕捉, 然后创建一个子进程, 等到子进程退出的时候看看会不会捕捉到17号信号就可以了? 所以, 下面为代码:
void handler(int signo)
{
cout << "catch a signal: " << signo << endl;
}
int main()
{
signal(17, handler);
pid_t id = fork(); //子进程返回0, 父进程返回子进程pid
if (id == 0)
{
while (true)
{
cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;
sleep(1);
break;
}
exit(0);
}
while (true)
{
cout << "I am a father process: " << getpid() << endl;
sleep(1);
}
return 0;
}
然后我们就会看到运行结果, 一秒过后, 父进程就受到了一个signal信号!
所以, 我们就知道, 子进程在等待的时候, 我们可以采用基于信号的方式进行等待!等待的好处是什么?
今天的基于信号方式进行等待还是要调用wait/waitpid这样的接口。 父进程一直得保证自己是一直在运行的(子进程不要孤儿) 那么如何做? ——就是把子进程等待写入信号的捕捉函数当中!!
我们利用下面的代码进行验证,我们想要的现象是前五秒程序父进程在跑, 子进程也在跑。 然后子进程退出, 接下来的五秒子进程变成教室, 父进程还在跑。 最后子进程被回收!
void handler(int signo)
{
sleep(5);
pid_t rid = waitpid(-1, nullptr, 0);
cout << "I am child process: " << getpid() << ", ppid: " << getppid() << "catch a signal: " << signo << endl;
}
int main()
{
signal(17, handler);
pid_t id = fork(); //子进程返回0, 父进程返回子进程pid
if (id == 0)
{
while (true)
{
cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;
sleep(5);
break;
}
exit(0);
}
while (true)
{
cout << "I am a father process: " << getpid() << endl;
sleep(1);
}
return 0;
}
运行结果:
我们就会发现中间正好五秒僵尸继承, 和我们预想的是一样的。
但是, 如果有多个子进程呢? 比如有10个子进程, 那么我们如果有一个子进程先退出。 然后捕捉信号, 然后其他子进程再一起退出。 但是这个时候17号信号屏蔽了。 所以其他子进程的信号阻塞了, 丢失了, 所以也就意味着我们最终只能够回收一两个进程。 其他的进程无法被回收。 遇到这种情况, 怎么才能正常处理呢? ——我们只要使用一个循环加非阻塞轮询, 就可以成功的解决这个问题,代码如下:
void handler(int signo)
{
sleep(5);
pid_t rid;
while (pid_t rid = waitpid(-1, nullptr, WNOHANG) > 0)
{
cout << "I am child process: " << getpid() << ", ppid: " << getppid() << "catch a signal: " << signo << endl;
}
}
int main()
{
signal(17, handler);
pid_t id = fork(); //子进程返回0, 父进程返回子进程pid
if (id == 0)
{
while (true)
{
cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;
sleep(5);
break;
}
exit(0);
}
while (true)
{
cout << "I am a father process: " << getpid() << endl;
sleep(1);
}
return 0;
}
这样, 我们的程序就能够将多个进程同时回收了!
——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!