信号保存和信号处置惩罚

打印 上一主题 下一主题

主题 1654|帖子 1654|积分 4964

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

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

x
目次


信号保存中告急的概念
内核中信号的保存
对sigset_t操作的函数
对block,pendding,handler三张表的操作
sigpromask
​编辑
sigpending
是否有sighandler函数呢?
案例
信号处置惩罚
操作系统是如何运行的?
硬件中断 
时钟中断
软中断
死循环
缺⻚中断?内存碎⽚处置惩罚?除零野指针错误?
明白用户态和内核态
信号的自界说捕捉 
sigaction
验证壅闭信号
处置惩罚完解除对信号的壅闭
验证在处置惩罚前将pending表清零 
验证sa_mask的作用
可重入函数
案例
volatile
SIGCHLD信号
waitpid增补
验证子进程退出会给父进程发送SIGCHLD信号
 基于信号对子进程回收
问题1:
办理方法循环回收
问题2: 




信号保存中告急的概念


信号捕捉的三种方式:1.默认 SIG_DFL  default
                                    2.忽略  SIG_ING    ingore
                                    3.自界说
信号递达:实际实行信号的处置惩罚动作
信号未决:信号从产生到递达的状态
信号壅闭:进程可以壅闭某个信号  -->壅闭/屏蔽特定信号,信号产生了,肯定把信号pendding(保存),并且信号永世不递达,除非解除壅闭。

os信号的保存都与以上的三个概念有关

壅闭 vs 忽略
壅闭发生在为信号递达之前
忽略发生在信号递达

内核中信号的保存

内核数据布局表示图

pending:信号未决
block: 信号壅闭
handler:信号递达

进程维护了以上的布局,以是进程是可以识别信号的。
以后对进程信号的操作都是为绕着这三张表睁开的。

内核中的相应的数据布局

 sigset_t 是信号集,也是信号屏蔽字(signal mask)(有block,pending)
雷同umask

对sigset_t操作的函数





对block,pendding,handler三张表的操作

sigpromask

读取/修改进程屏蔽字 block表 


set:进程中的block表修改基于set 
oldset:修改前block表的模样
how:以何种方式修改mask(block表)
1. SIG_BLOCK:  mask = mask | set  新增
2.SIG_UNBLOCK : mask = mask & ~set  去除指定信号
3.SIG_SETMASK: mask = set  覆盖

sigpending


通过该函数可以知道pending表的环境
但为什么不设置一个输入型参数,修改pending表呢?
因为信号的产生都是在修改pending表

是否有sighandler函数呢?

答案:没有
那么如何修改handler表呢?
signal就不停在修改啊
是否有名顿开的感觉啊。


案例

  1. #include <iostream>
  2. #include <unistd.h>
  3. #include <signal.h>
  4. #include <functional>
  5. #include <sys/types.h>
  6. #include <wait.h>
  7. #include <vector>
  8. void PrintBlock(const sigset_t &block)
  9. {
  10.     std::cout<<"block"<<"["<<getpid()<<"]: ";
  11.     for (int i = 31; i > 0; i--)
  12.     {
  13.         if (sigismember(&block, i))
  14.         {
  15.             std::cout << 1;
  16.         }
  17.         else
  18.         {
  19.             std::cout << 0;
  20.         }
  21.     }
  22.     std::cout << std::endl;
  23. }
  24. void handler(int signo)
  25. {
  26.     std::cout <<"signo:"<<signo<<" 信号递达"<<std::endl;
  27.     exit(0);
  28. }
  29. int main()
  30. {
  31.     signal(2,handler);
  32.     sigset_t block, oldblock;
  33.     sigemptyset(&block);
  34.     // 将二号新号加入
  35.     sigaddset(&block, 2);
  36.     //阻塞二号信号
  37.     int ret = sigprocmask(SIG_SETMASK, &block, &oldblock);
  38.     if (ret < 0)
  39.     {
  40.         perror("sigpromask");
  41.         exit(1);
  42.     }
  43.     int cnt = 0;
  44.     while (true)
  45.     {
  46.         PrintBlock(block);
  47.         sleep(3);
  48.         if(cnt==5)
  49.         {
  50.             PrintBlock(oldblock);
  51.             //取消对二号信号的阻塞
  52.             sigprocmask(SIG_SETMASK, &oldblock, nullptr);
  53.             
  54.         }
  55.         cnt++;
  56.     }
  57.     return 0;
  58. }
复制代码
 实行结果

一开始block表中二号信号被壅闭,纵然收到了二号信号也不能处置惩罚,当修改block表二号没被壅闭时,在实行二号信号相应的处置惩罚方法。

信号处置惩罚

信号没被处置惩罚,被保存起来---->合适的时候
那么什么是合适的时候?
进程从用户态切换会用户态时,检测额pending && block,决定是否用handler表处置惩罚信号。


os的运行状态:
1.用户态 ---> 实行用户写的代码
2.内核态  -->实行内核代码
信号自界说捕捉的流程



操作系统是如何运行的?

硬件中断 

硬件中断的处置惩罚流程
 

中断向量表在os启动时就添补好了。
时钟中断

进程是由操作系统调度的,那么操作系统谁有谁调度的呢?

有个设备时钟源定期向cpu发送中断,cpu处置惩罚中断,实行中断处置惩罚进程,而查表查到的终端服务是进程调度。
这样操作系统不就能自主调度了吗
时钟源是外设,将中断传给cpu效率有点低,如今的时钟源都是继续在cpu中。

主频就是控制CPU工作的时钟信号的频率
主频越快,相应单元时间内操作系统实行的中断服务越多,进程调度越频繁,os的性能越高。

进程调度不肯定切换进程

软中断


是否能不通过外设来产生中断呢?
可以,比如os支持系统调用,cpu设计了汇编指令(int 0x80 或 syscall),让cpu在内部触发中断逻辑,
这样就可已在软件中触发中断-->再根据中断号,查系统调用表,实行相应的系统调用

系统调用号的本质就是数组下标



死循环

无论是软中断还是硬件中断
os必要什么方法直接,向中断向量表中添加就可以了:os的本质就是一个死循环
  1. void main(void) /* 这⾥确实是void,并没错。 */
  2. { /* 在startup 程序(head.s)中就是这样假设的。 */
  3. ...
  4. /*
  5. * 注意!! 对于任何其它的任务,'pause()'将意味着我们必须等待收到⼀个信号才会返
  6. * 回就绪运⾏态,但任务0(task0)是唯⼀的意外情况(参⻅'schedule()'),因为任
  7. * 务0 在任何空闲时间⾥都会被激活(当没有其它任务在运⾏时),
  8. * 因此对于任务0'pause()'仅意味着我们返回来查看是否有其它任务可以运⾏,如果没
  9. * 有的话我们就回到这⾥,⼀直循环执⾏'pause()'。
  10. */
  11. for (;;)
  12. pause();
  13. } // end main
复制代码
---------------------------------------------------------------------------------------------------------------------------------

用户怎么把中断号给os?寄存器(如eax)
os怎么把返回值返回给用户? 也是寄存器
用户和内核之间通报信息用的是寄存器。

系统调用-->是通过软中断完成的
中断号-->系统调用号写入寄存器eax-->软中断-->查表--->实行相应的方法-->将返回值写入寄存器返回给用户

既然实行系统调用必要触发软中断,那我们使用系统调用函数值没有效 int 0x80 或 syscall?
因为linux提供的系统调用接口,不是C语言函数,而是  系统调用号 + 约定的通报参数 ,用于内核和用户间通报信息的 系统调用号和返回值的寄存器  + syscall  或 int 0x80
也就是说GNU glibc 把真正的系统调用封装--->C语言封装版的系统调用

OS是躺在中断处置惩罚进程上的代码块。

 

缺⻚中断?内存碎⽚处置惩罚?除零野指针错误?

这些错误都是通过软中断来处置惩罚的。


软中断有: 1.陷阱  (int 0x80 或 syscall)
                    2.非常
   CPU内部的软中断,⽐如int 0x80大概syscall,我们叫做 陷阱     •     CPU内部的软中断,⽐如除零/野指针等,我们叫做 非常。(以是,能明白“缺⻚非常”     为什么这么叫了吗?)     明白用户态和内核态

内核页表:整个系统只有一张
用户页表:每个进程都有一张
 对于任何进程,不管如何调度,任何进程都只能找到一个os
为什么只有一张内核页表?
因为用户最关心的是系统调用。
用户关心系统调用的地点吗?
不关心,只必要知道系统调用号就可以了。


系统调用是在进程的地点空间中发起的,但实在行是在内核地点空间中完成的。
1.调用任何函数(库中)都是在我们自己进程中的地点空间中进行的
操作系统无论怎么切换进程,进程都只能找到一个操作系统
2.os调用方法的实行是在内核地点空间中实行的
不管是通过哪个进程的地点空间进入内核,都是通过软中断进行操作的。

   ⽤⼾态就是执⾏⽤⼾[0,3]GB时所处的状态     •     内核态就是执⾏内核[3,4]GB时所处的状态       如何区分内核态还是用户态?    cpu中有cs段寄存器   
  用户如何进入内核态?
1.时钟/外设中断
2.非常   cpu--->软中断
3.陷阱    系统调用(int 0x80,syscall)

信号的自界说捕捉 


1.前三步很好明白,那么为什么要转为用户态来调用处置惩罚方法,直接在内核态来处置惩罚不好吗?
 内核和用户的权限不同,有安全风险;并且用户来处置惩罚,如果出了错误用户担责任。
2.信号处置惩罚完只能从内核返回,因为处置惩罚函数的调用与main()函数之间不存在调用关心(栈区)
3.如何继续实行原来的程序?
cpu中有pc寄存器(pc指针),指向下一条要实行的命令,只必要恢复pc指针的值就可以了。

sigaction

作用检查并修改handler表

sigaction布局 

sa_handler :函数指针
sa_mask:自界说屏蔽的信号 

信号处置惩罚期间,如果处置惩罚方法有系统调用,那么就陷入内核态
os不答应信号处置惩罚方法嵌套--->实现该方法的方式是将block表相应的信号置为1(壅闭该信号),但信号处置惩罚完会自动解除壅闭
什么时候pending表清0?
在调用信号处置惩罚之前,因为如果是处置惩罚完之后清零,那么就无法区分pending中的1是处置惩罚进程时 收到的1,还是处置惩罚完时的1                                                                                                                                                                                                                
验证壅闭信号

  1. void PrintBlock()
  2. {
  3.     sigset_t block;
  4.     sigprocmask(SIG_SETMASK,nullptr,&block);
  5.     for(int i = 31;i>0;i--)
  6.     {
  7.         if(sigismember(&block,i))
  8.             std::cout<<1;
  9.         else
  10.             std::cout<<0;
  11.     }
  12.     std::cout<<std::endl;
  13. }
  14. void handler(int signo)
  15. {
  16.     std::cout<<"signo: "<<signo<<std::endl;
  17.     while(true)
  18.     {
  19.         PrintBlock();
  20.         sleep(3);
  21.     }
  22. }
  23. int main()
  24. {
  25.     //signal(2,handler);
  26.     struct sigaction act,oldact;
  27.     act.sa_handler = handler;
  28.     sigaction(2,&act,&oldact);
  29.     //std::cout<<"执行"<<std::endl;
  30.     PrintBlock();
  31.     while(true)
  32.     {
  33.         pause();
  34.     }
  35. }
复制代码


处置惩罚完解除对信号的壅闭

  1. void PrintBlock()
  2. {
  3.     sigset_t block;
  4.     sigprocmask(SIG_SETMASK,nullptr,&block);
  5.     for(int i = 31;i>0;i--)
  6.     {
  7.         if(sigismember(&block,i))
  8.             std::cout<<1;
  9.         else
  10.             std::cout<<0;
  11.     }
  12.     std::cout<<std::endl;
  13. }
  14. void handler(int signo)
  15. {
  16.     std::cout<<"signo: "<<signo<<std::endl;
  17.     // while(true)
  18.     // {
  19.     //     PrintBlock();
  20.     //     sleep(3);
  21.     // }
  22.     PrintBlock();
  23.     sleep(3);
  24. }
  25. int main()
  26. {
  27.     //signal(2,handler);
  28.     struct sigaction act,oldact;
  29.     act.sa_handler = handler;
  30.     sigaction(2,&act,&oldact);
  31.     //std::cout<<"执行"<<std::endl;
  32.     PrintBlock();
  33.     while(true)
  34.     {
  35.         pause();
  36.         PrintBlock();
  37.     }
  38. }
复制代码

验证在处置惩罚前将pending表清零 

  1. #include <iostream>
  2. #include <unistd.h>
  3. #include <signal.h>
  4. #include <functional>
  5. #include <sys/types.h>
  6. #include <wait.h>
  7. #include <vector>
  8. void PrintBlock()
  9. {
  10.     sigset_t block;
  11.     sigprocmask(SIG_SETMASK, nullptr, &block);
  12.     std::cout<<"block: ";
  13.     for (int i = 31; i > 0; i--)
  14.     {
  15.         if (sigismember(&block, i))
  16.             std::cout << 1;
  17.         else
  18.             std::cout << 0;
  19.     }
  20.     std::cout << std::endl;
  21. }
  22. void PrintPending()
  23. {
  24.     sigset_t pending;
  25.     sigpending(&pending);
  26.     sigprocmask(SIG_SETMASK, nullptr, &pending);
  27.     std::cout<<"pending: ";
  28.     for (int i = 31; i > 0; i--)
  29.     {
  30.         if (sigismember(&pending, i))
  31.             std::cout << 1;
  32.         else
  33.             std::cout << 0;
  34.     }
  35.     std::cout << std::endl;
  36. }
  37. void handler(int signo)
  38. {
  39.     std::cout << "signo: " << signo << std::endl;
  40.     // while(true)
  41.     // {
  42.     //     PrintBlock();
  43.     //     sleep(3);
  44.     // }
  45.     PrintBlock();
  46.     PrintPending();
  47.     sleep(3);
  48. }
  49. int main()
  50. {
  51.     // signal(2,handler);
  52.     struct sigaction act, oldact;
  53.     act.sa_handler = handler;
  54.     sigaction(2, &act, &oldact);
  55.     // std::cout<<"执行"<<std::endl;
  56.     PrintBlock();
  57.     PrintPending();
  58.     while (true)
  59.     {
  60.         pause();
  61.         PrintBlock();
  62.         PrintPending();
  63.     }
  64. }
复制代码

 



验证sa_mask的作用

  1. void handler(int signo)
  2. {
  3.     std::cout << "signo: " << signo << std::endl;
  4.     // while(true)
  5.     // {
  6.     //     PrintBlock();
  7.     //     sleep(3);
  8.     // }
  9.     PrintBlock();
  10.     //PrintPending();
  11.     while(true)
  12.     {
  13.     }
  14.     //sleep(3);
  15. }
  16. int main()
  17. {
  18.     // signal(2,handler);
  19.     struct sigaction act, oldact;
  20.     act.sa_handler = handler;
  21.     sigset_t mask;
  22.     sigemptyset(&mask);
  23.     sigaddset(&mask,2);
  24.     sigaddset(&mask,3);//把三号信号屏蔽
  25.     act.sa_mask = mask;//设置屏蔽信号
  26.     sigaction(2, &act, &oldact);
  27.     // std::cout<<"执行"<<std::endl;
  28.     PrintBlock();
  29.     //PrintPending();
  30.     while (true)
  31.     {
  32.         pause();
  33.         //PrintBlock();
  34.         //PrintPending();
  35.     }
  36. }
复制代码

屏蔽了2号和3号信号 

可重入函数


一个函数被两个以上的实行流同时进入--->重入(重复进入)
重入,不出问题--->可重入函数
重入,出问题---> 不可重入函数--->涉及对全局资源的处置惩罚

大部分库中的函数都是不可重入函数
重入和不可重入没有优劣之分,只是特性。
案例


因为中断,将node2插入链表,head指向node2,但返回main函数继续实行时,head又指向了node1导致找不到node2,也就造成了内存泄漏。main和信号的处置惩罚是两个实行流,进入的都是inset函数。

volatile

volatile是易变关键字

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <unistd.h>
  4. int flag = 1;
  5. void handler(int signo)
  6. {
  7.     printf("signo:%d\n",signo);
  8.     printf("flag -> 0\n");
  9.     flag = 0;
  10. }
  11. int main()
  12. {
  13.     signal(2,handler);
  14.     while(flag);
  15.    
  16.     printf("quit normal!\n");
  17.     return 0;
  18. }
复制代码
 没开启编译器优化,会实时更新进程地点空间中flag的值


当把编译器的优化等级开高一些,为什么不退出了呢?

开了优化后,纵然进程内存地点空间中的flag改变了,但cpu中的flag不会根据进程地点空间中的flag更新寄存器中的flag,以是不停死循环没有退出 。
  1. volatile flag = 1;//加上volatile关键字
复制代码
纵然优化编译器优化等级开高了,也实时通过进程地点空间中值修改寄存器中的值。 


SIGCHLD信号

当子进程退回后----(给父进程发送)--->SIGCHLD信号
waitpid增补



pid为-1时,可以等候任何子进程。

验证子进程退出会给父进程发送SIGCHLD信号

  1. #include <iostream>
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. #include <signal.h>
  5. #include <wait.h>
  6. void handler(int signo)
  7. {
  8.     std::cout << "signo: " << signo << std::endl;
  9.     pid_t ret = waitpid(-1, nullptr, 0);//pid==-1,表示等待任何子进程
  10.     if (ret > 0)
  11.     {
  12.         std::cout<<"子进程: "<<ret<<"退出"<<std::endl;
  13.     }
  14.     else if (ret < 0)
  15.     {
  16.         std::cout << "wait error" << std::endl;
  17.     }
  18.     sleep(3);
  19. }
  20. int main()
  21. {
  22.     signal(SIGCHLD, handler);
  23.     pid_t pid = fork();
  24.     if (pid == 0)
  25.     {
  26.         std::cout << "我是子进程: ";
  27.         std::cout << getpid() << std::endl;
  28.         exit(0);
  29.     }
  30.     std::cout << "我是父进程: " << getpid() << std::endl;
  31.     while (true);
  32.     return 0;
  33. }
复制代码

 基于信号对子进程回收

问题1:

如果有n个子进程同时给父进程发送SIGCHLD信号,这是只能记录和处置惩罚一个信号;
如果pending记录了一个信号,其他的信号再发过来也不会被记录。
  1. #include <iostream>
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. #include <signal.h>
  5. #include <wait.h>
  6. void handler(int signo)
  7. {
  8.     std::cout << "signo: " << signo << std::endl;
  9.     pid_t ret = waitpid(-1,nullptr,0);
  10.     if(ret>0)
  11.     {
  12.             std::cout << "子进程: " << ret << "退出" << std::endl;
  13.     }
  14.     else if(ret<0)
  15.     {
  16.         std::cout<<"wait error"<<std::endl;
  17.     }
  18. }
  19. int main()
  20. {
  21.     signal(SIGCHLD, handler);
  22.     pid_t pid = fork();
  23.     for (int i = 0; i < 5; i++)
  24.     {
  25.         pid_t pid = fork();
  26.         if (pid == 0)
  27.         {
  28.             std::cout << "我是子进程: ";
  29.             std::cout << getpid() << std::endl;
  30.             exit(0);
  31.         }
  32.     }
  33.     while(true);
  34.     return 0;
  35. }
复制代码

创建了10个子进程只有7个子进程退出,剩余的子进程发出的信号没有被处置惩罚,这些子进程变为了僵尸

办理方法循环回收 



  1. void handler(int signo)
  2. {
  3.     while (true)
  4.     {
  5.         pid_t ret = waitpid(-1, nullptr, 0);
  6.         if (ret > 0)
  7.         {
  8.             std::cout << "子进程: " << ret << "退出" << std::endl;
  9.             // std::cout << "wait success" << std::endl;
  10.         }
  11.         else if (ret < 0)
  12.         {
  13.             std::cout << "暂时回收完毕" << std::endl;
  14.             break;
  15.         }
  16.     }
  17. }
复制代码
运行结果:子进程全都被回收 
 

没有僵尸子进程

问题2: 

基于问题1,如果10个子进程中,有几个进程迟迟不退出那么,就会壅闭在信号处置惩罚那里等候子进程的退出
办理方法

options中有个WNOHANG,如果没有子进程竣事则立刻返回0
这样处置惩罚方法就有壅闭变为了非壅闭,如果有子进程退出则又会在进入循环回收,一次往复,避免了子进程出现僵尸。 


  1. pid_t ret = waitpid(-1, nullptr, WNOHANG);
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表