文章目录
- 信号阻塞代码验证
- 验证信号的阻塞
- 验证信号的阻塞不影响信号注册
- 验证可靠信号不会丢信号,不可靠信号会丢信号
- 验证9号和19号信号不能被阻塞
- 用信号解决僵尸进程
- volatile关键字
信号阻塞代码验证
在上篇详解信号机制的博文中,我们提到了设置阻塞位图的函数sigprocmask函数:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
现在我们用代码来演示一下:
首先了解一下位图的设置函数:
验证信号的阻塞
代码如下:
1 #include <stdio.h>2 #include <unistd.h>3 #include <signal.h>4 void handler(int signum)5 {6 printf("signum : %d\n",signum);7 }8 int main(){9 signal(2, handler);10 sigset_t set;11 sigaddset(&set, 2); 12 sigprocmask(SIG_BLOCK, &set, NULL);13 while(1){14 printf("Hello!\n");15 sleep(1);16 }17 return 0;18 }
执行结果:
验证信号的阻塞不影响信号注册
验证可靠信号不会丢信号,不可靠信号会丢信号
验证思路:
我们选取两个信号2(非可靠信号)、40(可靠信号),首先把这两个信号阻塞,其次再接触这两个信号的阻塞,观察2号信号和40号信号处理几次,同时也证明可靠信号不能丢失信号,非可靠信号会丢失信号
代码如下:
1 #include <stdio.h>2 #include <unistd.h>3 #include <signal.h>4 void sigcallback(int sig){ 5 printf("sigcallback recv sig: %d\n", sig);6 } 7 8 int main(){9 signal(2, sigcallback);10 signal(40, sigcallback);11 12 sigset_t set;13 sigemptyset(&set);14 sigaddset(&set, 2);15 sigaddset(&set, 40);16 17 sigprocmask(SIG_BLOCK, &set, NULL);18 getchar(); 19 sigset_t oldset;20 sigemptyset(&oldset);21 sigprocmask(SIG_SETMASK, &oldset, NULL);22 23 while(1){ 24 printf("i am main, sleep(1)\n");25 sleep(1); 26 } 27 return 0;28 }
首先代码是阻塞的,我们发送多次信号,但是都是反应,接着我们随便敲了一个字符,此时启动getchar后面的代码,也就是解除阻塞,此时可以看到程序对之前阻塞了的信号的处理情况
执行结果:
验证9号和19号信号不能被阻塞
1 #include <stdio.h>2 #include <unistd.h>3 #include <signal.h>4 void handler(int signum)5 {6 printf("signum : %d\n",signum); 7 }8 9 int main(){10 signal(9, handler);11 signal(19, handler);12 13 sigset_t set;14 sigfillset(&set); // 位图全部置为115 16 sigprocmask(SIG_SETMASK,&set,NULL);17 while(1)18 {19 printf("Hello!\n");20 sleep(1);21 }22 23 return 0; 24 }
执行结果:
9号信号和19号信号不执行我自定义的处理方式,依旧按照原有方式处理信号
用信号解决僵尸进程
我们之前在如何解决僵尸问题的时候,提到了用wait或者waitpid函数,但是这两个函数都有不方便的地方,wait函数在调用时,父进程一直处于阻塞等待等待子进程退出状态,waitpid函数需要配合循环,这样的结果就是我们的父进程得不到充分利用,只等着回收子进程退出状态信息了,这里我们可以用信号,对子进程进行回收。
代码如下:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>void sigcallback(int sig){printf("sigcallback recv sig: %d\n", sig);wait(NULL);
}int main(){/** 目的: 解放父进程, 让父进程创建子进程之后, 还能执行父进程的代码逻辑。 并且,还能防止子进程变成僵尸进程** 做法:* 将SIGCHLD信号的处理方式进行自定义* 在自定义的函数当中调用wait/waitpid函数** 子进程退出之后, 会向父进程发送SIGCHLD信号* 父进程回调自定义处理函数, 从调用wait/waitpid函数, 回收子进程的退出状态信息* *//* 1. 自定义处理SIGCHLD信号 */signal(SIGCHLD, sigcallback);/** 2. 创建子进程了* */pid_t ret = fork();perror("fork");if(ret < 0){perror("fork");return 0;}else if(ret == 0){//childsleep(5);printf("i am child\n");}else{ //ret > 0//fatherwhile(1){printf("i am father, exec father process code\n");sleep(1);}}return 0;
}
执行结果:
volatile关键字
volatile关键字作用:保证内存可见性,告诉编译器,该变量的值可能会在程序的控制之外被修改,因此编译器不应该对该变量的读写进行优化或缓存。每次CPU要计算的数据都是从内存中获取,拒绝编译时优化的方案(从寄存器当中获取),gcc/g++的编译选项“-O0, O1, -O2,-O3“,优化级别越来越高。(理解优化级别越高,程序可能执行的越快)优化级别越高就从寄存器中取值的可能性越大。
代码演示:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>int g_val = 1;void sigcallback(int sig){printf("sigcallback recv sig: %d\n", sig);g_val = 0;
}int main(){signal(2, sigcallback);while(g_val){}printf("hhh, jump down\n");return 0;
}
运行结果:
上述代码运行按下ctrl+c向进程发送2号信号,进程直接结束了,说明g_val的值被修改了,这是因为我们在编译时没有对程序进行优化,这时候是从内存中拿的
我们试着优化一下,执行结果:
当我们试着将g_val用volatile关键字声明:
volatile int g_val = 1;
再执行:
此时优化就不管用了,保证了从内存中读取数据