[Linux]信号的处理与捕捉

目录

一.进程信号的理解

1.1 进程处理信号的过程 

1.2 信号经历的过程

1.2.1 产生信号的四种常见方式

1.2.2 信号的处理与捕捉

二.信号处理函数以及信号总览

 2.1 信号总览

2.1.1 2号信号 SIGINT

2.1.2 3号信号 SIGQUIT

2.1.3 9号信号SIGKILL(没有办法被捕捉的信号,杀死进程)

2.1.4 11号信号 SIGSEGV

2.2 信号捕捉的过程

2.2.1 没有成功递达信号

2.2.2 成功递达信号

2.3 信号捕捉函数补充

三.信号总结


一.进程信号的理解

1.1 进程处理信号的过程 

    进程在收到某种信号的时候,并不是会立刻进行处理的,因为有可能进程在处理优先级更高的事情,会选择在合适的时间进行处理这个信号(后续会进行详细介绍)。没有被处理的信号会被暂时保存起来,保存在进程控制块task_struct中,因为信号本质上其实也就是数据,向进程控制块task_struct中写入数据只能是由操作系统进行写入.

1.2 信号经历的过程

     信号从产生到被处理存在以下时间线:

1.2.1 产生信号的四种常见方式

1.外设设备的输入(常见诸如:键盘输入);

2.进程异常(进程异常通常是可以被反馈到硬件上面,从而给操作系统发送信号);

3.系统调用(调用系统级别的接口函数,也可以产生函数);

4.通过软件条件是可以产生信号的.

 在Linux下,输入kill -l指令可以观察到对应的信号信息,我们常见的信号通常包括1-31,这个联系到我们以前学过的知识,用位图存储相当方便,用对应的位置代表第几号信号,0/1则代表存在还是不存在,比如我们在收到了2号信号之后,就可以对应类似于0100 0000 0000 0000 0000 0000 0000 0000这样进行保存。

struct task_struct
{
    uint32_t sigmet_t;//0000 0000 0000 0000 0000 0000 0000 0000
}

1.2.2 信号的处理与捕捉

从信号的捕捉到处理,这个涉及到对应的三张表,block表(也称之为信号屏蔽字),pending表,handler表(信号的捕捉方式,有对应三种方式:1.默认;2.忽略;3.自定义).

二.信号处理函数以及信号总览

信号的处理函数signal函数,signal函数就是建立起某个信号和赌赢处理函数的连接,只有真正接受到对应的信号的时候才会执行对应的处理函数.

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

第一个参数,对应是信号的序号,上面已经列出了Linux下的信号序号,对应既可以用序号,也可以使用信号名;

第二个参数,是接收到信号以后处理信号的函数指针;(三种方式:默认,忽略,自定义);

返回值:调用成功,就会返回上一次信号处理函数被调用的返回值;

调用失败,则会返回SIG_ERR,因为存在无法找到对应的函数的情况,所以就无法创建起信号和信号处理函数之间的关联;

signal(6,signalhandler);
//建立起6号信号与signalhandler函数之间的关联
//等实际收到6号信号的时候,就会对应执行signalhandler捕捉函数

 2.1 信号总览

向命令行输入kill -l对应就可以看到在Linux系统下已经定义好的信号:

 目前,暂且我们只需要了解前31个信号,我们下面会以代码的方式进行说明一下相应的信号.

2.1.1 2号信号 SIGINT

2号信号的作用是终止进程,快捷方式是Ctrl+C.

Linux系统测试:

//信号捕捉函数
void signalhandler(int signo)
{
  printf("process %d receive No.%d signal\n",getpid(),signo);
  //退出码设置成2
  exit(2);
}

int main()
{
   //捕捉2号信号
   signal(2,signalhandler);   
   while(1)
   {      
     sleep(1);
     printf("process %d is running\n",getpid());
   }
             
  return 0; 
}

程序运行如下:

刚开始,我们不发送指定的信号,进程一直在运行.

 

摁下Ctrl+C之后,向系统发送2号信号,进程退出,并且捕捉到2号信号,调用相应的函数,从结果上面来看我们发送的就是2号信号.

 

2.1.2 3号信号 SIGQUIT

相同的原理,捕获3号信号,快捷键就是Ctrl + \.

2.1.3 9号信号SIGKILL(没有办法被捕捉的信号,杀死进程)

9号信号的作用就是:杀死进程的同时无法被捕捉.

2.1.4 11号信号 SIGSEGV

11号信号时在进程崩溃的时候发送的.

我们在进行观察信号的代码的时候,首先让进程崩溃,观察程序.

很明显,进程崩溃了,所示如下图:

我们使用signal函数试试,看看接收到的是几号信号。

 因此,我们可以得出一个结论,当进程崩溃退出的时候,实际上是接受到了第11号信号!

2.2 信号捕捉的过程

2.2.1 没有成功递达信号

没有成功递达信号的方式有两种:1.没有收到信号;2.信号被阻塞;进入内核态,访问操作系统的代码,对应的pending(信号未决)表为0,代表没有信号进行传递,直接返回用户态,执行下一句代码.

2.2.2 成功递达信号

假如现在发送来一个信号,这里假如收到的是3号信号,pending表和block表其实就是一个位图结构,对应的几号位置代表几号信号,0/1则代表信号是否被接受到;在进程的task_struct表中先看pending表是否为1,在看block表(也称之为信号屏蔽字)是否可以被递达,然后成功递达到handler表中。handler表相当于一个函数指针数组,默认存放函数指针,有三种处理方式:

1.默认

2.忽略

3.自定义处理

收到对应的函数处理方式之后,切换回用户态进行处理.

在处理完对应的函数之后,不是直接就去执行用户代码的下一句了!!!因为在这里同处用户态的代码,不能够直接进行转换,要想返回到用户态时,是从内核态执行相应的底层接口函数sys_sigreturn(),从而进行返回到用户态上面去.

下面,以下面的一张图来进行解释,Linux下信号处理的过程:

1.首先执行用户层代码,然后进入内核区执行OS代码(用户态->内核态)

2.进入内核态后,要进行监测是否收到信号,通过访问进程的task_struct的三张表{pending(信号未决表),block(信号屏蔽字),handler(信号处理表)};如果pending表为0代表没收到信号,返回用户态执行下一句代码,并且pending表为1,block表也为1代表信号被阻塞不能够被正常接受,也是会返回到代码段中执行下一句代码;如果成功检测到有信号发出并成功递达,执行对应的信号处理函数.(内核态->用户态).

3.执行完对应的信函处理函数之后,不能够直接返回代码段,调用系统接口sys_sigreturn()函数(用户态->内核态)

4.最后,返回代码段继续执行下一句代码.(内核态->用户态

图中的红色圆圈处则代表进程状态改变的节点 

2.3 信号捕捉函数补充

当信号的信号处理函数被执行的时候,该信号的block表对应的位置是会被设置成1,这样就会保证如果该类信号再次产生,会被阻塞到当前处理结束为止,等到处理结束之后,则会恢复原来的信号屏蔽字.

sigaction函数:检查或者修改与指定信号相关联的处理动作(可以同时两种操作)

1.第一个参数是信息序号(我们已经列出信号列表)

2.第二个参数是输入型参数,我们需要在处理对应的捕捉函数的同时,还可能暂时屏蔽掉其他的信号,可以将这些参数传入结构体中

3.第三个参数是输出型参数,也是一个结构体,用于输出我们上一次是如何被设置的,如果我们不关心的话,可以直接设置成NULLPTR。

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

struct sigaction结构体介绍: 

struct sigaction {
    void (*sa_handler)(int);
    //void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    //int sa_flags;
    //void (*sa_restorer)(void);
}

这里的结构体名称只是恰好和函数名一致,两者没有任何的联系,既要能设置handler表的指针,又要能够暂时性地屏蔽掉其他的信号,所以Linux把其需要传入的参数设置成了一个结构体sigaction.我们只需要重点关注上面两个参数就可以.

测试代码:使用sigaction函数,处理函数的同时,屏蔽2号信号.

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
//信号处理函数
void sighandler(int signo)
{
   while(1)
   {
    sleep(1);
    printf("process %d receive signal No.%d\n",getpid(),signo);
   }
}

int main()
{
  struct sigaction iact;
  memset(&iact,0,sizeof(iact));
  iact.sa_handler= sighandler;
  //屏蔽2号信号
  sigset_t set;
  sigemptyset(&set);
  sigaddset(&set,2);
  iact.sa_mask=set;
  //当有3号信号递达时,执行处理函数
  sigaction(3,&iact,NULL);
  //阻断进程退出
  while(1)
  {
   printf("No.%d process wait signal\n",getpid());
   sleep(1);//阻断进程退出

  }
  return 0;
}

程序运行如下:

此时,没有3号信号被递达系统执行运行代码,等待信号.

 

3号信号被递达,调用信号处理函数

 

键盘输入2号信号,信号被阻塞.

 

进程退出.

 

三.信号总结

用一张图来表示我们学到的所有内容:

 

 

本期博客就到此为止,如有疏漏和错误,请读者多多指出,谢谢大家!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胡须不排序H

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值