Linux —— 信号

文章详细阐述了Linux系统中信号的产生方式,包括通过键盘、系统调用、硬件异常和软件条件。讨论了信号的Term和Core终止,以及信号的保存机制,如PCB中的pending和block位图。此外,还介绍了信号的递达过程,包括用户态和内核态的转换以及信号处理。最后,提到了可重入函数、volatile关键字和SIGCHLD信号在进程管理中的作用。
摘要由CSDN通过智能技术生成

目录

1.预备知识

2.信号的产生方式

2.1通过键盘

2.2通过系统调用

2.3通过硬件异常产生信号

2.4软件条件产生异常

3.信号的Term终止和Core终止

4.信号的保存

4.1PCB的具体内容

5.信号的递达

5.1用户态和内核态

5.2信号递达的过程

6.有关信号的接口

6.1用户层位图

6.2使用案例

6.3sigaction

7.可重入函数

8.volatile关键字

9.SIGCHLD信号

1.预备知识

信号是给进程发送的。如果一个信号到来,进程会做什么?首先进程必须识别信号,识别信号有两个关键点:认识信号;收到此信号后要做的动作。通常,信号的识别由程序员通过代码处理。

在Linux下如何查看信号?使用如下命令:

kill -l                        ===>查看信号

其中,1~31称为普通信号,34~64称为实时信号。本片文章只谈论普通信号。

当某个进程收到一个信号后,它不一定马上处理信号,进程很可能在做其他任务。进程的任务执行和信号的产生是异步的。那么收到信号到处理这个信号之间是存在一个时间窗口的,也就是说进程必须记住这个信号,也就是说进程必须有对信号的保存能力(接收信号但不处理)。进程处理信号有三种动作:采用信号的默认动作;自定义信号的动作;忽略信号的动作。

信号保存在进程的进程控制块(PCB)中,其中有一个unsigned int signal字段,用位图结构描述信号。假设进程收到3号信号,那么就把第三个比特位由0置1。发送信号的本质就是修改pcb中的信号位图。因为pcb属于操作系统维护的数据结构对象,其管理者是操作系统,也就是说,只有操作系统才有权力修改位图结构。

信号的发送方式有多种,但无论是哪种,其本质都是通过操作系统向目标进程发送信号,也就是说,操作系统必须提供相关的系统调用。kill命令的底层也一定调用了系统调用。

2.信号的产生方式

2.1通过键盘

我们可以使用Ctrl+C热键组合来终止前台进程。其本质就是被操作系统识别,然后解释为2号信号。我们可以通过以下命令来查看详细的信号手册:

man 7 signal                        ===>查看信号手册

如果想让Ctrl+C热键组合不做信号的默认动作,我们可以使用signal系统调用来自定义信号的行为。

代码如下:

#include <iostream>
using namespace std;
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>


void sigcb(int signo)
{
    cout << "捕捉到信号:" << signo << endl;
}
int main()
{
    signal(SIGINT,sigcb);
    while(true)
    {
        cout << "我是一个进程 pid:" << getpid() << endl;
        sleep(1);
    }
    return 0;
}

 

可以看到,Ctrl+C并没有终止掉前台进程,而是调用了我们自定义的函数。需要注意的是,signal这个接口仅仅是设置将要到来的信号的行为,也就是说,调用了signal之后,会将2号信号的行为更改未为我们自定义的行为,而不是直接执行我们自定义的行为。

2.2通过系统调用

操作系统提供了关于信号的接口,也就说明了操作系统发送信号的能力是为用户准备的,用户发起一个请求,操作系统再去执行。

最常用的系统调用便是kill命令(前面说过kill命令底层一定调用了系统调用),它可以向任意进程发送任意信号。

raise系统调用能够给自己发送任意信号,我们以一段代码演示:

#include <iostream>
using namespace std;
#include <signal.h>
#include <unistd.h>
int main()
{
    while(true)
    {
        cout << "运行中..." << endl;
        sleep(1);
        raise(9);//给自己发送9号信号
    }
    return 0;
}

abort系统调用能够给自己发送指定信号(6号信号),我们以一段代码演示:

#include <iostream>
using namespace std;
#include <signal.h>
#include <unistd.h>
int main()
{
    while(true)
    {
        cout << "运行中..." << endl;
        sleep(1);
        abort();
    }
    return 0;
}

 大部分情况下,进程收到的大部分信号的默认动作都是终止进程。即使它们的动都相同,但是信号不以动作的同与不同划分,而是各种信号代表不同的时间,也就是说,不同的信号代表不同的终止原因。例如前面文章提到的管道,当管道的读端关闭时,写端就没有存在的意义了(操作系统不允许存在任何铺张浪费行为),此时操作系统向写端进程发送一个信号,进而终止进程。

2.3通过硬件异常产生信号

信号的产生不一定非得用户显式发送。当我们写一段程序,有除0操作。我们知道计算的过程是交给cpu的,那么操作系统为什么能够知道我们除0?还能给进程发送一个终止信号?

int main()
{
    int a = 3;
    a /= 0;
    return 0;
}

其原因在于:cpu内部有一个状态寄存器,用来衡量某次运算的结果。这个寄存器有一个溢出标记位,当标记位溢出,则表示当前计算结果们没有意义,不需要被采纳。也就是说,除0会产生一个无穷大的值,从而导致状态寄存器的标记位溢出,然后发生cpu运算异常。而操作系统是要对所有硬件管理的,所以操作系统自然而然地知道了cpu发生了异常,然后操作系统又能对软件做管理,很轻松地找到了是谁导致地异常,然后再给导致异常的进程发送一个8号信号(我们看到的错误信息就是8号信号导致的)。

 我们对上面的代码进行改造以下,将会看到一个暂时无法理解的现象:

#include <iostream>
using namespace std;
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>

void mySig(int signo)
{
    cout << "收到信号:" << signo << endl;
    sleep(1);
}
int main()
{
    signal(8,mySig);
    while(true)
    {
        cout
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小龙向钱进

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

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

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

打赏作者

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

抵扣说明:

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

余额充值