• Linux篇16进程信号第二部分


    1.深入理解用户态&&内核态

    我们知道进程在收到信号的时候可能不是立即去处理信号的,而是要等到“合适的时候”。这个合适的时候指的是“从内核态切换回用户态的时候”。

    内核态通常用来执行OS代码,是一种权限非常高的状态。用户态是一种用来执行普通用户代码的状态,是一种受监管的约束状态。

    image-20220806143536180

    image-20220806145115903

    进程无论如何切换都能看到操作系统,但是不一定都能访问

    2.内核如何实现信号的捕捉

    image-20220806150613255

    捕捉方式即上图所示,下面我们来学习一下sigaction函数

    #include 
    int sigaction(int signo, const struct sigaction *act, struct sigaction *oact)
        
    struct sigaction {
        void     (*sa_handler)(int);
        void     (*sa_sigaction)(int, siginfo_t *, void *);
        sigset_t   sa_mask;
        int        sa_flags;
        void     (*sa_restorer)(void);
    };
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作.

    我们如果想使用该函数自定义处理信号的方式,只需要自定义一个handler赋给sa_handler,将sa_mask使用sigemptyset函数清空再将sa_flags赋值0即可,结构体内其他内容我们不需要关心,下面我们写一段代码测试一下

    #include                                           
    #include 
    #include 
    #include 
    struct sigaction act, oact;
    void handler(int signo)
    {
      printf("get a signal: %d\n", signo);
      //再将信号的操作恢复成最初的样子
      sigaction(SIGINT, &oact, NULL);
    }
    int main()
    {
      //先将结构体清空
      memset(&act, 0, sizeof(act));
      memset(&oact, 0, sizeof(oact));
      
      act.sa_handler = handler;
      act.sa_flags = 0;
      sigemptyset(&act.sa_mask);
    
      sigaction(SIGINT, &act, &oact);//以SIGINT信号测试一下
      while(1)
      {
        printf("I am a process\n");
        sleep(1);
      }
      return 0;
    }       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    image-20220806153528363

    3.浅谈可重入函数

    一个函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入。如果因为重入造成错乱,这样的函数称为不可重入函数,反之就是可重入函数。

    符合以下任一条件的函数都是不可重入函数:

    • 调用了malloc或free,因为malloc是用全局链表来管理堆的。
    • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

    4.volatile

    这是c语言的一个关键字。我们在讲这个关键字之前先看这样一段代码

    image-20220806161219967

    这段的代码逻辑很简单,当进程没有收到2号信号的时候,while是一个死循环,直到收到2号信号,flag变为1,此时while循环不成立,打印process quit,然后进程结束。

    这样的代码看似没有任何问题,可是当编译器的优化级别较高时就会出现一直死循环的情况。我们先将优化级别调高试试

    Makefile

    mytest:mytest.c
    	gcc -O3 -o $@ $^//-O3是最高级别优化
    .PHONY:clean
    clean:
    	rm -f mytest
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20220806161908011

    为什么会出现这种情况呢?

    image-20220806162554626

    这个时候如果我们使用volatile关键字,即使优化级别高,也不会出现这种情况了

    image-20220806163428850

    volatile的作用就是保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作。

    5.SIGCHLD信号

    我们前面讲过wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻 塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不 能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一 下,程序实现复杂。

    其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自 定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程 终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。

    下面我们就自定义SIGCHLD信号的处理方式来验证一下

      1 #include <stdio.h>                                           
      2 #include <signal.h>
      3 #include <string.h>
      4 #include <stdlib.h>
      5 #include <unistd.h>
      6 #include <sys/wait.h>
      7 #include <sys/types.h>
      8 void handler(int signo)
      9 {
     10   printf("get a signal: %d\n", signo);
     11   int ret = 0;
     12   while((ret = waitpid(-1, NULL, WNOHANG)) > 0)//为什么这里要循环,因为可能有多个信号需要回收
     13   {
     14     printf("wait child %d success\n", ret);
     15   }
     16 }
     17 int main()
     18 {
     19   signal(17, handler);
     20   if(fork() == 0)
     21   {
     22     //child
     23     printf("child will quit, pid: %d\n", getpid());
     24     sleep(3);
     25     exit(2);
     26   }
     27   while(1)
     28   {}
     29 
     30   return 0;
     31 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    image-20220806165623903

    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    四六级成绩快速查询指南 & 项目上传github实现定时自动推送教程
    七天学会C语言-第六天(指针)
    2023高教社杯 国赛数学建模B题思路 - 多波束测线问题
    二叉树的最大深度
    ArrayList源码分析
    VRTK4⭐一.VRTK4和VRTK的区别 , 及VRTK4简介
    归并排序和计数排序
    sqlserver 查询数据显示行号
    【每日一题】ABC155D - Pairs | 二分双指针 | 简单
    react源码分析:实现react时间分片
  • 原文地址:https://blog.csdn.net/btzxlin/article/details/126198229