今天为大家带来的是我学习完Linux中信号章节的相关知识,主要内容有:信号的概念,信号的产生,注册,注销,处理和阻塞,并且还有应用等知识点。本次使用的编程语言为C语言,使用的操作系统为Linux操作系统。

信号的概念

       信号:是一种软件中断,也是一种时间的通知机制。通知进程发生了某件事,打断进程当前的操作,然后去处理这件事。

       上述的解释可能有点模糊,我们可以这样理解:当我们的电脑运行一个程序时,我们点击关闭程序,我们“点击关闭”的动作就是向电脑发送一个信号,那个信号就是“关闭程序”。

       接下来我们来看看信号的种类,打开我们的Linux,输入代码即可看到全部种类的信号,共62种

Kill -l//查看全部信号指令

信号初解_信号

       其中,用红色圈出的信号为非可靠信号,即非实时信号;没有圈出的则是可靠信号,即实时信号,其中的区别我会在注册中给出说明。这些信号的左右我们可以通过代码找到解释:

man 7 signal//信号解释

       一个信号的生命周期为:产生注册注销处理,另外还有特殊的阻塞状态,下面我们就来一一刨析每个信号的每个阶段到底经历了什么。

产生

信号的产生可分为两类:硬件产生和软件产生。

硬件产生包括:

       CTRL+C(从键盘中输入SIGINT信号,中断进程)

       CTRL+Z(从键盘中输入SIGTSTP信号,停止进程运行)

       CTRL+\(从键盘中输入SIGQUIT信号,使进程退出)

软件产生:就是通过kill -signum pid命令对指定进程发送信号,还可以通过kill(),raise(),abort(),alarm()函数对进程发送信号,感兴趣的hxd可以自己查阅这些函数的功能。

       这里我们要说下kill命令,本质使默认给进程发送一个终止信号,进程接收到信号后,进行处理,处理结果就是退出。但是有三种情况,kill命令不能退出信号:1.僵尸进程,我们无法杀死已经死去的进程。2.进程处于停止状态,对发送的信号不做处理。3.信号可能被阻塞或被自定义处理。

注册

       信号的注册,主要是为了让进程知道自己接收了某种信号,本质就是在进程pcb种做出相应位置的标记。下面我们来详细说明并解释非可靠信号和可靠信号的区别。

       进程中的pcb中有一个sigpending(未决信号集),即位图,会标记出进程接收到了某个信号,其次还有一个sugqueue链表,会添加一个信号的信息节点。当有一个信号被进程接收时,位图会在相应的位置置1,并在链表中添加节点。这就是信号注册的本质。(未决信号:还没有被处理的信号)

信号初解_信号_02

其次就是可靠信号,当有可靠信号注册时,如果没有信号注册,位图置1,并添加节点;如果有节点注册,直接添加信号节点即可。

对于非可靠信号,当有信号没有被注册时,位图置1,并添加节点;如果有节点被注册时,直接丢弃该信号。

这就是可靠和非可靠的区别,即是否丢失数据。

       举个例子,当我们是一个进程睡眠30秒,我们使用kill命令,系统会直接发送信号打断休眠,去处理kill信号—退出;而当我们使用CTRL+Z进入停止状态,再使用kill命令,此时出现的情况为1.信号以被注册,只是没有被处理2.一旦解除阻塞,则信号直接被处理,杀死进程。

注销

       信号注销,也就是消除信号在未决信号集中的痕迹,防止系统重复处理。注销也分为两类:可靠信号的处理和非可靠信号的处理。

非可靠信号的注销:由于位图中每种非可靠信号只能有一个,所以非可靠信号的处理为删除信号节点,将位图置0。

可靠信号的注销:删除信号节点,并判断是否有相同节点存在,若没有则位图置0。

处理

信号的处理:调用信号的事件处理函数。总共分为三大类:

1.默认处理:系统中已经预定义好的处理方式

2.忽略处理:空的处理方式

3.自定义处理:程序员自定义的一个处理方式,替换原有的处理方式

我们这里说明下忽略处理和自定义处理。

       忽略处理最典型的就是SIGCHLD信号,当子进程推出后,默认给父进程的就是SIGCHLD信号,父进程接收信号后,就会忽略子进程的退出,从而导致僵尸进程的产生。

下面我们来演示自定义进程如何书写:

首先介绍接口:

信号初解_信号_03

接下来我们来实现代码:

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sigcb(int n)//handler函数的格式
{
printf("n:%d\n",n);
}
int main()
{
signal(SIGINT,sigcb);//替换中断信号
while(1)
{
printf("hello world!\n");
sleep(1);
}
return 0;
}

当我们CTRL+C退出时,就会出现程序替换,进程不退出。

信号初解_信号_04

阻塞

       阻塞,是进程的一种状态,该状态可以使进程接收到的数据不被处理,但是信号依旧可以被注册。

       进程pcb中有个block阻塞信号集合,如果有信号被阻塞就会添加到block阻塞集合中,当处理信号时,就不会处理该信号。

信号初解_信号_05

       下面我们就来实现阻塞状态下进程接收信号的过程,该过程不太好直观体现,所以我们使用上述signal函数,进行信号替换,让大家可以直观看出问题。

首先来介绍下接口:

信号初解_信号_06

下来我们开始写代码:

#include<stdio.h>
#include<signal.h>
#include<unistd.h>

void sigcb(int n)//替换的函数handler
{
printf("recv signal:%d\n",n);
}
int main()
{
signal(SIGINT,sigcb);
signal(SIGRTMIN+5,sigcb);

sigset_t set,old;//定义两信号集合

sigemptyset(&set);//清空定义的信号集合
sigemptyset(&old);
sigfillset(&set);//将所有信号填充到set中

sigprocmask(SIG_BLOCK,&set,&old);//将set中的信号阻塞
printf("按回车,继续运行!!!\n");
getchar();
sigprocmask(SIG_SETMASK,&old,NULL);//将原先的进程状态替换
while(1)
{
sleep(1);
}
return 0;
}

我们运行程序,我们发送设定好的信号自定义处理,就可以看到进程中没有信号被处理;当我们嗯下回车,信号就会被处理。

信号初解_信号_07

应用

下面我们来说两个比较常见的信号应用吧

1.自定义或者忽略处理SIGPIPE信号,例如:管道所有写端都被关闭,继续write就会触发异常。

2.自定义或者忽略处理SIGCHLD信号,例如:子进程退出时给父进程发送信号,然后称为僵尸进程。可以将SIGCHLD信号自定义为SIG_IGN信号,系统就会在子进程退出后默认直接处理,就不会发生僵尸进程。

我们再来看看上述所讲的自定义处理方式的信号捕捉流程,一幅图直接解决

信号初解_信号_08

好了,本次信号的内容就到这里,希望大家可以从中学到知识。如果有什么不对的地方,希望大佬可以批评指正!!!