Linux操作系统 - 信号

目录

signal函数

信号的产生

信号处理

相关操作函数

信号捕捉


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

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

Linux操作系统 - 信号_第1张图片

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

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

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

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

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


signal函数

Linux操作系统 - 信号_第2张图片

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

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

#include
#include
#include
using namespace std;

void handler(int sig) //自定义处理函数                   
{
  cout<<"catch a signal "<

Linux操作系统 - 信号_第3张图片

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

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

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

信号的产生

1、通过键盘输入

例如ctrl c,ctrl \

2、通过系统函数

kill函数

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

#include
#include
#include
using namespace std;

void handler(int sig)
{
  cout<<"catch a signal "<

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

 Linux操作系统 - 信号_第4张图片

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

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

 

3、通过软中断

SIGPIPE信号(管道相关)

SIGALRM信号

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

4、硬件错误

比如说越界发生的段错误

#include                       
#include
#include
using namespace std;

void handler(int sig)
{
  cout<<"catch a signal "<

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

Linux操作系统 - 信号_第5张图片

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


信号处理

Linux操作系统 - 信号_第6张图片

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

信号递达(handler)

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

信号未决(pending)

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

信号阻塞(block)

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

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

Linux操作系统 - 信号_第7张图片

举个例子:

Linux操作系统 - 信号_第8张图片

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

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

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

相关操作函数

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

sigprocmask函数

用来修改block的函数

Linux操作系统 - 信号_第9张图片

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

Linux操作系统 - 信号_第10张图片

假设当前信号屏蔽字为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。

Linux操作系统 - 信号_第11张图片

 

sigpending函数

Linux操作系统 - 信号_第12张图片

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

例子

#include                            
#include
#include
using namespace std;

void show_pending(sigset_t pending)//按位显示pending信号集
{
  for(int i=1;i < 32;i++)
  {
    if(sigismember(&pending,i))
      cout<<"1";
    else 
      cout<<0;
  }
  cout<

Linux操作系统 - 信号_第13张图片

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

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

信号捕捉

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

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

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

Linux操作系统 - 信号_第14张图片

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

Linux操作系统 - 信号_第15张图片

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

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

问题

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

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

你可能感兴趣的:(linux,linux,进程,操作系统,信号,进程间通信)