初步理解Linux中的信号概念以及信号产生

目录

信号

理解信号

进程是如何从键盘得到信号的

信号的产生

1、键盘组合键

2、kill命令

3、系统调用

4、异常

5、软件条件


理解信号

1. 生活角度的信号

你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能"识别快递"

当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不 是一定要立即执行,可以理解成"在合适的时候去取"。 在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你"记住了有一个快递要去取"

当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:

  1. 执行默认动作(幸福的打开快递,使用商品)

  2. 执行自定义动作(快递是零食,你要送给你你的女朋友)

  3. 忽略快递(快递拿上来之后,继续开一把游戏) 快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话

2、技术角度的信号

在我们的云服务器中,我没每登录一次shell就会让见一个bash子进程,由于Linux系统规定,每次只允许有一个前台进程,可有多个后台进程,当我们在运行前台进程的时候,可以在命令行上输入ctrl+c来让我们的程序终止,这也就是在发送终止信号

注意:

1、Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。

  1. Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号。

  2. 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步 (Asynchronous)的。

信号的概念十分简单,就是:进程之间事件异步通知的一种方式,属于软中断

根据上面我们所说的背景知识,将我们的进程当作人,快递当作信号,我们可以得出下面的结论

1、进程必须能够**识别和处理信号,**需要存在处理信号的能力,属于进程内置功能

2、当进程收到信号时,进程可能不会立即处理,会在合适的时候进行处理

3、一个进程从收到信号,到处理信号之间的时间,叫做时间窗口,此时进程需要能够保存信号

4、信号处理的方式:a、默认动作(大部分为终止),b、忽略,c、自定义动作(动作的捕捉)

进程是如何从键盘得到信号的

当我们的计算机在设计的时候,为了方便检查我们外设上面的资源,采取了一种中断 的方法,中断就是指CPU暂停正在运行 的服务,转向 为中断源服务,结束之后在继续回到原来工作进行处理的一种方式,当我们的外设上面存在数据之后,我们的外设通过硬件(中断单元)的响应,让Cpu知道我们某一外设上存在数据,然后控制OS进行数据读取,然后送到用户缓冲区,当我们的操作系统对输入数据进行解析之后,发现是信号,就会在OS内维护的一张中断向量表内寻找中断方法。再次传递给我们进程。

信号的产生

信号产生有五种方式:

1、键盘组合键

可以通过键盘组合键来产生信号,例如ctrl+c(2号信号),ctrl+'\'(3号信号)

ctrl+c(2号信号),进程终止

ctrl+'\'(3号信号)

2、kill命令

Linux中 kill 命令可以向指定进程发送指定的信号,从1号到31号信号我们叫做普通信号,34号到64号叫做实时信号 ,其中缺少了32和33号。在1到31号信号中,不可以自定义的信号有9好和19号 ,其他的信号可以进行信号捕捉。

3、系统调用

这里介绍三个系统调用接口

signal

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

功能:进行信号捕捉,自定义信号操作(9号19号除外)

参数:signum:信号编号

handler:自定义函数

返回值可以忽略

kill

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid, int sig);

功能·:向指定进程发送指定的命令

参数:pid 进程的pid

sig 信号参数

返回值:成功为0,失败为-1

raise

#include <signal.h>

int raise(int sig);

功能:给自己发送指定信号

参数:sig 信号参数

返回值:成功为0,失败为 -1

abort

#include <stdlib.h>

void abort(void);

功能:给当前进程发送6号信号(注意:调用该函数时,就算捕捉了6号信号还是会退出进程)

4、异常

我们目前最熟悉的异常一个就是野指针和除以0了,在信号中分别对应11号和8号信号,当我们发送这样的异常时,尝试去捕捉8号信号

cpp 复制代码
void myhandler(int sing)
{
    cout << "process get signal : " << sing << endl;
}
int main()
{
    signal(8, myhandler);

    int a = 10;
    int b = 0;
    while (true)
    {
        a /= b;
        cout<<"please cout me"<<endl;
        sleep(1);
    
    return 0;
}

运行结果如下:我们会发现,他会一直调用我们的handler函数,其他代码不会进行执行。

现象解释:在我们的进程在被调度的过程中,我们的CPU中存在一个状态寄存器,他会保存我们当前进程的状态,在其中有一个bit位代表着溢出,当我们除以零的时候,这个计算值就会趋近于武器大,导致溢出位异常,当我们的CPU每次运行这个进程的时候,都为带着这个进程的上下文来CPU中进行计算,当我们的溢出标志位一旦溢出,就会发送8号信号,但是我们对信号进行了捕捉,导致这个进程并没有终止,所以我们的溢出标志位一直存在,导致无论什么时候我们在运行这个进程的时候,都会检测溢出标志位,发送8号信号,导致我们自定义函数一直被调用.

5、软件条件

我们在管道的时候以及学过了SIGPIPE信号,它就是一种软件条件产生的信号,当我们的管道的读端被关闭的时候,写端会产生SIGPIPE信号来关闭进程

我们再来介绍一个函数alarm

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号,该信号的默认处理动作是终止当前进程。

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,"以前设定的闹钟时间还余下的时间"就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数

验证一下:

cpp 复制代码
int main()
{
    alarm(5);
    while (true)
    {
        cout << "I an running" << endl;
        sleep(1);
    }
}

结果刚好在五秒后关闭

相关推荐
后来后来啊2 小时前
20261.23 &1.24学习笔记
笔记·学习·算法
鱼跃鹰飞2 小时前
LeetCode热题100:5.最长回文子串
数据结构·算法·leetcode
tobias.b2 小时前
408真题解析-2010-10-数据结构-快速排序
java·数据结构·算法·计算机考研·408真题解析
季明洵2 小时前
力扣反转链表、两两交换链表中的节点、删除链表的倒数第N个节点
java·算法·leetcode·链表
历程里程碑2 小时前
Linux 4 指令结尾&&通过shell明白指令实现的原理
linux·c语言·数据结构·笔记·算法·排序算法
zhengtianzuo2 小时前
049-Linux抓屏-xcb
linux·抓屏·xcb
Zfox_2 小时前
【Docker#2】容器化虚拟化
运维·后端·docker·容器
normanhere2 小时前
华为交换机堆叠问题总结
服务器·数据库·华为
fanruitian2 小时前
k8s 设置副本数
linux·容器·kubernetes