当前位置:   article > 正文

Linux操作系统 - 信号_signal 11

signal 11

目录

signal函数

信号的产生

信号处理

相关操作函数

信号捕捉


信号是进程之间事件异步通知的一种方式,属于软中断

首先看看有哪些信号 kill -l

编号为34以后的信号为实时信号。

之前在进程操作的文章中讲过kill -9是用来杀掉一个进程,给指定的进程发送SIGKILL信号,好比告诉进程你该结束了。

还有在进程间通信中,在利用管道进行进程间通信时,当读数据的进程退出后,写数据的进程会收到系统发来的13号信号SIGPIPE。

或者平时在命令行输入ctrl c 其实是前台向正在运行的进程发送了2号信号SIGINT。

其实每个信号都有自己的默认的处理函数,对捕捉到的信号进行执行默认的处理函数,部分信号可以自定义处理函数。


signal函数

第一个参数signum是信号的编号,表明要重新定义几号信号

第二个参数是一个函数指针,返回值是void,参数是int类型。这个函数指针就是指向用户要自定义处理动作的函数。

  1. #include<iostream>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. using namespace std;
  5. void handler(int sig) //自定义处理函数
  6. {
  7. cout<<"catch a signal "<<sig<<endl;
  8. }
  9. int main()
  10. {
  11. signal(2,handler);//自定义捕捉
  12. while(true)
  13. {
  14. cout<<"i am running..."<<endl;
  15. sleep(1);
  16. }
  17. return 0;
  18. }

此时在ctrl c发现终止不了程序,原因是我们已经修改了2号信号默认的处理函数。(此时在退出用ctrl \(这个是发送的3号信号SIGQUIT))

注意:9号信号不能自定义捕捉

所有的信号都必须经过OS发出给各个进程。

信号的产生

1、通过键盘输入

例如ctrl c,ctrl \

2、通过系统函数

kill函数

kill -  信号名或者信号编号   进程pid

  1. #include<iostream>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. using namespace std;
  5. void handler(int sig)
  6. {
  7. cout<<"catch a signal "<<sig<<endl;
  8. }
  9. int main()
  10. {
  11. //把2、3号信号自定义捕捉
  12. signal(2,handler);
  13. signal(3,handler);
  14. while(true)
  15. {
  16. cout<<"i am running..."<<endl;
  17. sleep(1);
  18. }
  19. return 0;
  20. }

运行程序,先看一下他的pid,

 

此时ctrl c和ctrl \都不可以结束掉进程。

可以发送9号信号,kill -9 

 

3、通过软中断

SIGPIPE信号(管道相关)

SIGALRM信号

通过alarm函数进行定时,时间到了会给当前进程发送SIGALRM信号,该信号的默认操作是终止当前进程。

4、硬件错误

比如说越界发生的段错误

  1. #include<iostream>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. using namespace std;
  5. void handler(int sig)
  6. {
  7. cout<<"catch a signal "<<sig<<endl;
  8. sleep(1);
  9. }
  10. int main()
  11. {
  12. //signal(11,handler);//将11号信号自定义捕捉
  13. int *p=nullptr;
  14. *p = 10;
  15. return 0;
  16. }

如果自定义捕捉,可以发现是11号信号。

虚拟地址映射真实地址的过程出错,实际上是硬件mmu等报错,由操作系统接收。


信号处理

当信号产生时,并不是立即处理的,会先进入一个状态(未决),等到合适的时机才会进行处理。

信号递达(handler)

信号的处理动作,包括默认自定义捕捉忽略

信号未决(pending)

是一种状态(产生到递达之间的状态)。

信号阻塞(block)

进程可以阻塞一个信号,被阻塞的信号一旦产生,将一直处于未决状态,直到进程解除对此信号的阻塞,才会执行递达。

由于信号是发送给进程的,进程肯定有相关的数据结构

举个例子:

例如1号信号SIGHUP的block =0,pending = 0,说明信号未被阻塞,信号也未产生。

2号信号SIGINT的block = 1,pending = 1,说明信号已经产生,处于未决状态,但是又由于信号被阻塞了,所以在等待解除阻塞。

3号信号SIGQUIT的block = 1,pending = 0,信号未产生,但是可以被阻塞。

相关操作函数

在用户这一端,我们只能去修改block标志位,使得让某些信号进入阻塞状态。

sigprocmask函数

用来修改block的函数

第一个参数how决定要以哪种方式修改block。

假设当前信号屏蔽字为mask 

SIG_BLOCK方式的意思是:mask = mask | set (add set)

SIG_UNBLOCK :mask = mask&~set (remove set)

SIG_SETMASK:mask= set (equal)

第二个参数set,用户传入设置block,具体意思根据第一个参数

第三个参数oldset,记录前一次的屏蔽字

信号集操作函数

sigset_t声明的变量,sigset_t其实是 unsigened long

接下来这一组函数本质上是用来位操作的,设置好block的值之后,传入上面的sigprocmask函数的set。

 

sigpending函数

用来读取当前进程未决信号集。

例子

  1. #include<iostream>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. using namespace std;
  5. void show_pending(sigset_t pending)//按位显示pending信号集
  6. {
  7. for(int i=1;i < 32;i++)
  8. {
  9. if(sigismember(&pending,i))
  10. cout<<"1";
  11. else
  12. cout<<0;
  13. }
  14. cout<<endl;
  15. }
  16. void handler(int sig)
  17. {
  18. cout<<"catch a signal "<<sig<<endl;
  19. }
  20. int main()
  21. {
  22. //定义信号集
  23. sigset_t pending;
  24. sigset_t block,oldblock;
  25. //初始化
  26. sigemptyset(&pending);
  27. sigemptyset(&block);
  28. sigemptyset(&oldblock);
  29. //将2号信号阻塞
  30. sigaddset(&block,2);
  31. sigprocmask(SIG_BLOCK,&block,&oldblock);
  32. cout<<"开始阻塞"<<endl;
  33. //打印pending信号集
  34. for(int i=0;i < 5;i++)
  35. {
  36. sigpending(&pending);
  37. show_pending(pending);
  38. sleep(1);
  39. }
  40. //自定义捕捉
  41. signal(2,handler);
  42. //解除阻塞
  43. sigprocmask(SIG_UNBLOCK,&block,nullptr);
  44. cout<<"解除阻塞"<<endl;
  45. while(1);
  46. return 0;
  47. }

可以看到当发送2号信号后,2号信号处于pending状态。一旦解除阻塞,就会递达(合适的时机)

相对应的未决信号就会被清空

信号捕捉

合适的时机:从内核态切换到用户态时,才进行相关的检测

捕捉信号:信号处理动作是用户自定义的函数。

如果不是用户自定义的函数,即系统默认的处理函数,只需要一次用户到内核状态切换的过程。

如果是用户自定义的捕捉函数

在自定义捕捉函数时,总共需要四次切换,用户到内核、内核到用户切换各两次。

举个例子: 用户程序注册了SIGQUIT信号的处理函数sighandler。当前正在执行main函数,这时发生中断或异常切换到内核态。在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数, sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行。

问题

为什么要这么复杂而不直接在内核调用信号处理函数?

内核权限更高,调用用户自定义的处理函数有风险,之所以引入内核和用户两种模式,也是为了操作系统的安全。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/806722
推荐阅读
相关标签
  

闽ICP备14008679号