【Linux】进程间通信之信号机制2

文章目录

  • 信号阻塞代码验证
    • 验证信号的阻塞
    • 验证信号的阻塞不影响信号注册
    • 验证可靠信号不会丢信号,不可靠信号会丢信号
    • 验证9号和19号信号不能被阻塞
  • 用信号解决僵尸进程
  • volatile关键字

信号阻塞代码验证

在上篇详解信号机制的博文中,我们提到了设置阻塞位图的函数sigprocmask函数:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

现在我们用代码来演示一下:

首先了解一下位图的设置函数:

【Linux】进程间通信之信号机制2_第1张图片

验证信号的阻塞

代码如下:

  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 }

执行结果:
【Linux】进程间通信之信号机制2_第2张图片

验证信号的阻塞不影响信号注册

验证可靠信号不会丢信号,不可靠信号会丢信号

验证思路:

我们选取两个信号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后面的代码,也就是解除阻塞,此时可以看到程序对之前阻塞了的信号的处理情况

执行结果:

【Linux】进程间通信之信号机制2_第3张图片

验证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); // 位图全部置为1
 15 
 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 
#include 
#include 
#include 

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){
        //child
        sleep(5);
        printf("i am child\n");
    }else{ //ret > 0
        //father
        while(1){
            printf("i am father, exec father process code\n");
            sleep(1);
        }
    }
    return 0;
}

执行结果:

【Linux】进程间通信之信号机制2_第4张图片

volatile关键字

volatile关键字作用:保证内存可见性,告诉编译器,该变量的值可能会在程序的控制之外被修改,因此编译器不应该对该变量的读写进行优化或缓存。每次CPU要计算的数据都是从内存中获取,拒绝编译时优化的方案(从寄存器当中获取),gcc/g++的编译选项“-O0, O1, -O2,-O3“,优化级别越来越高。(理解优化级别越高,程序可能执行的越快)优化级别越高就从寄存器中取值的可能性越大。

代码演示:

#include 
#include 
#include 

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的值被修改了,这是因为我们在编译时没有对程序进行优化,这时候是从内存中拿的

我们试着优化一下,执行结果:

【Linux】进程间通信之信号机制2_第5张图片

当我们试着将g_val用volatile关键字声明:

volatile int g_val = 1;

再执行:

【Linux】进程间通信之信号机制2_第6张图片
此时优化就不管用了,保证了从内存中读取数据

你可能感兴趣的:(Linux,linux,运维,服务器)