linux--进程信号详解

1.引入信号的概念

信号是软件中断。它给我们提供了一种能够异步处理事件的方法。事实上,进程并不知道信号何时到来。

比如,当我们的某一个进程失去控制,而我们想让他终止运行时,通常使用Ctrl+c的方式使进程强制终止,Ctrl+c虽然由硬件产生,但是它在操作系统内核中会被视作SIGINT信号(signal interrupt 中断信号)。

信号也与版本有关,不同的操作系统所支持的信号种类和数量有差异(如下图),本篇博客仅讲解linux操作系统中的信号。
在这里插入图片描述

2.信号的生命周期

linux下进程一般经过4个步骤:
信号的产生–>信号的注册–>信号的注销–>信号的处理,下面我们分步讲解信号的各个阶段。

3.信号的产生

在linux操作系统中,有很多条件能够产生信号:

  1. 通过硬件中断产生信号:

     比如在中断使用Ctrl+c,Delete等按键可以产生硬件中断。
    
  2. 程序执行出现异常会产生信号

     在程序中,某些错误会让操作系统内核该程序对应的进程产生相应的信号。
     比如,当程序中有对无效内存做引用时,操作系统内核会产生并发送SIGSEGV信号给该进程
    
  3. 使用者用kill(1)在命令行,向某个进程发送指定的信号,(或者在某个进程调用kill(2)函数接口亦可)

     在另一个终端给某个进程发送 kill -n  pid 即可给pid所对应的进程发送n号信号。
     
     在某个进程中使用int kill(pid_t pid,int sig)向pid进程发送sig号信号。
    
  4. 当进程满足某些软件条件时,也会产生信号

     比如当子进程退出后,会给父进程发送SIGCHLD信号,提示父进程接收他的退出信息,方便释放子进程资源。
    

4.信号的注册

4.0递达/未决/未决信号集/未决信号链

在讲解信号注册之前,要先理解两个概念:

递达:执行信号所代表的系统调用函数叫做递达。
未决:从信号产生到递达之前的之一段时间叫信号未决。

下面我们来讲讲信的注册。

首先,要明白信号是如何存储的。
在这里插入图片描述

在进程PCB中有3张位图block,pending,handler,他们分别标志这阻塞信号,未决信号,以及当前信号的处理方式。

struct sigpending pending://未决信号的数据成员
struct sigpending{
struct sigqueue *head, **tail;//信号集的头尾指针
sigset_t signal;                   //未决信号集,每一位都标志着一个信号。
};

struct sigqueue{                  //未决信号链                  
struct sigqueue *next;
siginfo_t info;
}

信号在进程中注册指的就是:

信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,
并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。
只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
4.1可靠信号与不可靠信号

linux下进程信号有62种,分为两类,其中1-31号为不可靠信号,34-64为可靠信号。

不可靠信号又称非实时信号,当一个非实时信号要插入sigqueue未决信号链之前,操作系统会先查看pending位图该信号的位置是否已经被置为1,如果是,则直接丢弃该信号。

可靠信号又称实时信号,实时信号不管pending位图中该信号位置是0还是1,都会插入sigqueue中。

所以,我们可以这样想,sigqueue中最多只能有一个不可靠信号的节点信息,但可能有多个可靠信号的信息。

4.2 信号集及操作函数
信号集被定义为一种数据类型:

typedef struct 
{
   unsigned long sig[_NSIG_WORDS];
} sigset_t;

信号集用来描述信号的集合,每个信号占用一位。
Linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。

下面是为信号集操作定义的相关函数:

#include <signal.h>

int sigemptyset(sigset_t *set)
//初始化由set指定的信号集,信号集里面的所有信号被清空;(每创建一个信号集一定要记得调用)


int sigfillset(sigset_t *set)
//调用该函数后,set指向的信号集中将包含linux支持的64种信号,即填充信号集;


int sigaddset(sigset_t *set, int signum);
//在set指向的信号集中加入signum信号;


int sigdelset(sigset_t *set, int signum)
//在set指向的信号集中删除signum信号;


int sigismember(const sigset_t *set, int signum)
//判定信号signum是否在set指向的信号集中。


int sigpending(sigset_t *set);
//   将当前pending集合(信号注册集合)中的信号取出来放到set中
 

对阻塞信号集操作主要用到的是sigprocmask函数

int sigprocmask(int how,sigset_t *set,sigset_t oldset);
how:
	SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号

	SIG_UNBLOCK 如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞

	SIG_SETMASK 更新进程阻塞信号集为set指向的信号集

set:与阻塞信号集进行交互的信号集

oldset:保存当前阻塞信号集,方便回退。

 

说了这么多,我们来实际操作一下:

  1 #include<iostream>
  2 #include<unistd.h>
  3 #include<signal.h>
  4 using namespace std;
  5 
  6 void sig_print(sigset_t *set)
  7 {
  8     int i=0;
  9     for (;i<32;i++)//打印set
 10     {
 11         if (sigismember(set,i))
 12         {
 13             cout<<"1";
 14         }
 15         else
 16         {
 17             cout<<"0";
 18         }
 19 
 20     }
 21     cout<<endl;
 22 
 23 }
 24 
 25 int main()
 26 {
 27     sigset_t s,p;//创建信号集s,p
 28 
 29     sigemptyset(&s);//将信号集的内容置空
 30     sigaddset(&s,SIGINT);//将SIGINT信号添加进信号集s中
 31     sigprocmask(SIG_BLOCK,&s,NULL);
 		//将信号集s中的信号添加进当前进程的block位图。这里也就是将SIGINT阻塞了。
 32 
 33 
 34     while(1)
 35     {
 36         sigpending(&p);//取出当前进程的pending位图放进p中
 37         sig_print(&p);
 38         sleep(1);
 39     }
 40 
 41 
 42 
 43     return 0;
 44 }

这段程序就是将SIGINT信号阻塞掉,然后持续输出pending位图,那么我们来运行一下试试:
在这里插入图片描述
我们发现,就算使用Ctrl+c也无法结束这个进程,原因就是Ctrl+c这个硬件中断在操作系统看来就是一个SIGINT信号,发送给该进程后,由于block中阻塞了该信号,所以进程迟迟不能结束。

但是我们还是能够用Ctrl+\结束掉进程的,Ctrl+\给进程发送的是SIGQUIT信号。

5.信号的注销

如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。

对于不可靠信号(非实时信号)来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);

而对于可靠信号(实时信号)来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则执行完相应的处理函数后应该把信号在进程的未决信号集中删除(信号注销完毕)。否则待该信号的所有sigqueue处理完毕后再在进程的未决信号集中删除该信号。

6.信号的处理

信号的处理分成3种:

  1. 默认处理方式(1.生成core文件然后结束进程 2.继续执行 3.直接结束进程 4.暂停此进程 5.想父进程发送我已经执行结束,等待回收资源.)

  2. 忽略处理方式(如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。)

  3. 自定义处理方式,让信号按照你自定义的函数执行。

我们主要讨论的是第三种,自定义的处理方式。

6.1信号的捕捉

在进程处理信号之前,进程要先捕捉到这个信号,那么,进程是怎么捕捉到信号的呢?是信号刚注册完毕就立即捕捉并处理吗?如果信号阻塞又怎样呢?这些都是我们要考虑的问题。

下面我们用一个例子来讲讲信号从产生到处理的全过程。
在这里插入图片描述
举例:

  1. 用户注册了SIGQUIT信号的处理函数sighandler.。

  2. SIGINT注册进进程的pending位图中,sighandler注册进handler位图中。

  3. 当前正在执行main函数,这时发生中断或异常切换到内核态.

  4. 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达.

  5. 进程识别到SIGINT信号的处理方式是用户自定义函数且信号无阻塞。

  6. 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数. sighandler和main函数使用不同的堆栈空间,他们之间不存在调用和被调用的关系,是两个独立的控制流程.

  7. sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态

  8. 如果没有新的信号抵达,这次再返回用户态就是恢复main函数的上下文继续执行了

6.2 信号处理函数
在这里插入代码片
6.3 cure-dump

后续补充

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值