linux信号 | 信号的增补知识

[复制链接]
发表于 2026-1-12 18:12:07 | 显示全部楼层 |阅读模式
        前言:本节内容紧张是一些linux信号的周边知识大概增补知识。 对于信号的学习, 学习了信号概念, 产生, 生存与捕捉就已经算是认识我们的信号了。 如果想要知道更多关于信号的知识也可以看一下本篇文章。 
          ps:本篇内容适当相识信号的友友们举行观看哦
  目次
sigaction
信号递达后pending位图清零的机遇
 信号处理处罚时的屏蔽
sa_mask 
函数重入
volatile关键字
SIGCHLD

sigaction


        这内里第一个参数signum就雷同于signal内里的signum, 都是用来重新设置某个信号的处理处罚动作。 第二个参数我们会发现它的范例名称和我们的函数名称是一样的, 第三个参数也是这个范例。  那么这里的第二个参数实在是一个输入型参数。 第三个参数是一个输出型参数。此中第二个参数就是传送我们本身界说的自界说捕捉方法。 第三个参数就是将对应的信号的老的处理处罚方法给我们传出来。
        然后我们看一下sigaction范例的界说:

        上面就是sigaction的界说, 但是内里有很多成员都是和及时信号干系的, 我们不关心。 我们只关心第一个和第三个成员变量。 此中第一个就是我们将来捕捉信号要实行的处理处罚方法。
信号递达后pending位图清零的机遇

           当我们在处理处罚某个信号的时间, 内核会将当前信号的pending位图由1清零。标题是, 是我们处理处罚之前清零呢, 照旧处理处罚之后清零呢 ? 这个我们就可以在handler函数内里打印一下我们的pending位图, 就能观察到到底是在那里清零的, 下面的实行方法就是——我们ctrl + C, 这个时间给进程发送信号pending位图2号位被置为1. 此时我们处理处罚这个信号, 我们在捕捉信号内里打印pending位图, 如果是处理处罚之前清零, 那么就打印一串0, 但是如果是处理处罚之后清零, 那么就打印一串1。下面为代码
  代码
  1. #include<iostream>
  2. #include<cstring>
  3. #include<signal.h>
  4. #include<unistd.h>
  5. using namespace std;
  6. void PrintPending()  //打印位图结构
  7. {
  8.     sigset_t set;
  9.     sigpending(&set);
  10.     for (int signo = 1; signo <= 31; signo++)
  11.     {
  12.         if (sigismember(&set, signo)) cout << "1" << endl;
  13.         else cout << "0";
  14.     }
  15.     cout << endl;
  16. }
  17. void handler(int signo)
  18. {
  19.     PrintPending();
  20.     cout << "catch a signal: " << signo << endl;
  21. }
  22. int main()
  23. {
  24.     struct sigaction act, oact;  //这里可以省略struct,不省略是因为能够提醒我们是系统结构体。
  25.     memset(&act, 0, sizeof(act));
  26.     memset(&oact, 0, sizeof(oact));
  27.     act.sa_handler = handler;
  28.     sigaction(2, &act, &oact);
  29.    
  30.     while (true)
  31.     {
  32.         cout << "I am a process: " << getpid() << endl;
  33.         sleep(1);
  34.     }
  35.     return 0;
  36. }
复制代码
运行效果:
  

   信号处理处罚时的屏蔽

           那么, 这里另有一个标题就是操纵体系会将当前的信号加入到信号屏蔽字当中。 当信号处理处罚函数返回的时间, 主动规复原来的信号屏蔽字。也就是说, 当某个信号在被处理处罚的时间, 是不能再次被递达的。 防止信号捕捉的时间, 发生各种嵌套调用。
          那么怎样证明信号捕捉的时间, 这个信号确实被屏蔽了呢? 这里我们用一个步调来举行实行。 ——实行的流程就是我们的处理处罚信号内里是一个死循环, 循环打印pending位图。 然后循环外会打印一句捕捉到信号。如果我们实行2号信号时没有屏蔽2号信号, 那么我们发送频频2号信号, 那么都应该被递达, 那么捕捉到信号就会频发的打印。 但是如果屏蔽的话就应该不会被递达, 也就只打印一句捕捉到信号:
  1. #include<iostream>
  2. #include<cstring>
  3. #include<signal.h>
  4. #include<unistd.h>
  5. using namespace std;
  6. void PrintPending()  //打印位图结构
  7. {
  8.     sigset_t set;
  9.     sigpending(&set);
  10.     for (int signo = 1; signo <= 31; signo++)
  11.     {
  12.         if (sigismember(&set, signo)) cout << "1" << endl;
  13.         else cout << "0";
  14.     }
  15.     cout << endl;
  16. }
  17. void handler(int signo)
  18. {
  19.     cout << "catch a signal: " << signo << endl;
  20.     while (true)
  21.     {
  22.         PrintPending();
  23.     }
  24. }
  25. int main()
  26. {
  27.     struct sigaction act, oact;  //这里可以省略struct,不省略是因为能够提醒我们是系统结构体。
  28.     memset(&act, 0, sizeof(act));
  29.     memset(&oact, 0, sizeof(oact));
  30.     act.sa_handler = handler;
  31.     sigaction(2, &act, &oact);
  32.    
  33.     while (true)
  34.     {
  35.         cout << "I am a process: " << getpid() << endl;
  36.         sleep(1);
  37.     }
  38.     return 0;
  39. }
复制代码
运行效果:
  

  可以看到只有第一次ctrl + C递达了信号, 其他时间的信号都没有被递达。 而且第二次ctrl + c后pending表2号为变成了1, 分析这个信号此时未决状态。 也可以推测被屏蔽了。 
  sa_mask 

别的sigaction内里另有一个成员变量交sa_mask。 这个sa_mask是sigset_t范例, 如哦正在处理处罚2号信号, 那么2号信号就会主动被体系屏蔽。 如果我还想屏蔽更多信号呢? 也就是说, 我们在捕捉2号期间, 如果sa_mask默认, 那么就只会屏蔽2号信号。 但是如果我们想要屏蔽更多的信号, 就要用sa_mask举行设置。 
   代码:
  1. #include<iostream>
  2. #include<cstring>
  3. #include<signal.h>
  4. #include<unistd.h>
  5. using namespace std;
  6. void PrintPending()  //打印位图结构
  7. {
  8.     sigset_t set;
  9.     sigpending(&set);
  10.     for (int signo = 1; signo <= 31; signo++)
  11.     {
  12.         if (sigismember(&set, signo)) cout << "1";
  13.         else cout << "0";
  14.     }
  15.     cout << endl;
  16. }
  17. void handler(int signo)
  18. {
  19.     cout << "catch a signal: " << signo << endl;
  20.     while (true)
  21.     {
  22.         PrintPending();
  23.         sleep(1);
  24.     }
  25. }
  26. int main()
  27. {
  28.     struct sigaction act, oact;  //这里可以省略struct,不省略是因为能够提醒我们是系统结构体。
  29.     memset(&act, 0, sizeof(act));
  30.     memset(&oact, 0, sizeof(oact));
  31.     act.sa_handler = handler;
  32.     sigemptyset(&act.sa_mask); //先清空一下samask
  33.     sigaddset(&act.sa_mask, 1); //添加1号比特位为1
  34.     sigaddset(&act.sa_mask, 3); //添加1号比特位为1
  35.     sigaction(2, &act, &oact);
  36.    
  37.     while (true)
  38.     {
  39.         cout << "I am a process: " << getpid() << endl;
  40.         sleep(1);
  41.     }
  42.     return 0;
  43. }
复制代码
运行效果:
  

  然后我们就会发现, 处理处罚2号信号的时间, 1, 3号信号都被屏蔽了。 
  
函数重入

如果我们有下面的节点界说, 头插函数和自界说捕捉动作
现在我们有下面这个链表

现在我们实行insert。 

此时的链表布局是如许的:

由于信号捕捉又是插入一个新节点:

 以是, 就会再次进入insert函数插入新节点。那么此时的链表节点就是如许的:

末了, 信号捕捉完毕后, 再将原来的insert函数实行完, 就变成了最闭幕果, 如下:

           以上这种情况就是一个函数被重复进入了, 也叫做函数重入。 而且, 上面的listnode3我们称为节点发生了丢失。 即由于节点丢失导致的内存走漏标题。 
          上面有两个标题, 这两个标题合起来就是, 函数重入导致的节点丢失标题。 而我们把这种重入后会有标题的函数称为不可重入函数。即: 如果一个函数被重复进入的情况下, 堕落了, 大概大概堕落, 我们就把他叫做不可重入函数。 否则叫做可重入函数。  可不可重入是特点, 不含批驳。 现在我们学过的大部门函数, 都是不可重入的。 
volatile关键字

volatile在信号的角度怎么明白呢? 这里我们通过一串代码来举行明白。 起首写下下面的代码:
  1. int flag = 0;
  2. int main()
  3. {
  4.     while (!flag);
  5.    
  6.     cout << "process quit success" << endl;
  7.     return 0;
  8. }
复制代码
 对于这串代码, 这串代码正常情况下是不能退出的, 会不停死循环。但是我们这里重新界说一下2号信号的处理处罚方法:
  1. int flag = 0;
  2. void handler(int signo)
  3. {
  4.     cout << "catch a signal: " << signo << endl;
  5.     flag = 1;
  6. }
  7. int main()
  8. {
  9.     signal(2, handler);
  10.     while (!flag);
  11.    
  12.     cout << "process quit success" << endl;
  13.     return 0;
  14. }
复制代码
如许, 如果我们捕捉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并不会检测到, 以是我们的步调就不停死循环了。 
           而volatile的核心作用就是防止编译器过分优化, 保持内存的可见性!!!凡是用volatile做修饰的变量, 就是在告诫编译器不要做任何优化。 
  SIGCHLD

        子进程退出并不是单纯的静偷偷的退出, 当它退出的时间, 它会向父进程发送信号, 这个信号就是SIGCHLD(17号。 怎么证明会发送这个信号呢? ——是不是我们父进程对17号信号举行捕捉, 然后创建一个子进程, 比及子进程退出的时间看看会不会捕捉到17号信号就可以了? 以是, 下面为代码:
  1. void handler(int signo)
  2. {
  3.     cout << "catch a signal: " << signo << endl;
  4. }
  5. int main()
  6. {
  7.     signal(17, handler);
  8.     pid_t id = fork();   //子进程返回0, 父进程返回子进程pid
  9.     if (id == 0)
  10.     {
  11.         while (true)
  12.         {
  13.             cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;
  14.             sleep(1);
  15.             break;
  16.         }
  17.         exit(0);
  18.     }
  19.     while (true)
  20.     {
  21.         cout << "I am a father process: " << getpid() << endl;
  22.         sleep(1);
  23.     }
  24.     return 0;
  25. }
复制代码
然后我们就会看到运行效果, 一秒事后, 父进程就受到了一个signal信号! 

        以是, 我们就知道, 子进程在等待的时间, 我们可以接纳基于信号的方式举行等待!等待的长处是什么?
   

  •         可以获取子进程的退出状态。 
  •         开释子进程的僵尸。
  •         通过壅闭大概非壅闭轮询, 我们固然不知道父子进程谁先运行, 但是父进程肯定是末了退出的!
        本日的基于信号方式举行等待照旧要调用wait/waitpid如许的接口。 父进程不停得包管本身是不停在运行的(子进程不要孤儿) 那么怎样做? ——就是把子进程等待写入信号的捕捉函数当中!!
        我们使用下面的代码举行验证,我们想要的征象是前五秒步调父进程在跑, 子进程也在跑。 然后子进程退出, 接下来的五秒子进程变成课堂, 父进程还在跑。 末了子进程被采取!
  1. void handler(int signo)
  2. {
  3.     sleep(5);
  4.     pid_t rid = waitpid(-1, nullptr, 0);
  5.     cout << "I am child process: " << getpid() << ", ppid: " << getppid() << "catch a signal: " << signo << endl;
  6. }
  7. int main()
  8. {
  9.     signal(17, handler);
  10.     pid_t id = fork();   //子进程返回0, 父进程返回子进程pid
  11.     if (id == 0)
  12.     {
  13.         while (true)
  14.         {
  15.             cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;
  16.             sleep(5);
  17.             break;
  18.         }
  19.         exit(0);
  20.     }
  21.     while (true)
  22.     {
  23.         cout << "I am a father process: " << getpid() << endl;
  24.         sleep(1);
  25.     }
  26.     return 0;
  27. }
复制代码
运行效果:
        我们就会发现中心恰好五秒僵尸继续, 和我们预想的是一样的。  
        但是, 如果有多个子进程呢? 好比有10个子进程, 那么我们如果有一个子进程先退出。 然后捕捉信号, 然后其他子进程再一起退出。 但是这个时间17号信号屏蔽了。 以是其他子进程的信号壅闭了, 丢失了, 以是也就意味着我们终极只可以大概采取一两个进程。 其他的进程无法被采取。 遇到这种情况, 怎么才气正常处理处罚呢? ——我们只要使用一个循环加非壅闭轮询, 就可以乐成的办理这个标题,代码如下:
  1. void handler(int signo)
  2. {
  3.     sleep(5);
  4.     pid_t rid;
  5.     while (pid_t rid = waitpid(-1, nullptr, WNOHANG) > 0)
  6.     {
  7.         cout << "I am child process: " << getpid() << ", ppid: " << getppid() << "catch a signal: " << signo << endl;
  8.     }
  9. }
  10. int main()
  11. {
  12.     signal(17, handler);
  13.     pid_t id = fork();   //子进程返回0, 父进程返回子进程pid
  14.     if (id == 0)
  15.     {
  16.         while (true)
  17.         {
  18.             cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;
  19.             sleep(5);
  20.             break;
  21.         }
  22.         exit(0);
  23.     }
  24.     while (true)
  25.     {
  26.         cout << "I am a father process: " << getpid() << endl;
  27.         sleep(1);
  28.     }
  29.     return 0;
  30. }
复制代码
         如许, 我们的步调就可以大概将多个进程同时采取了!
——————以上就是本节全部内容哦, 如果对友友们有资助的话可以关注博主, 方便学习更多知识哦!!! 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表