Linux进程信号的产生、保存、处理
一、信号的概念
信号是一种向目标进程发送消息, 异步通知的一种机制,属于软中断。本质上是用软件来模拟中断行为!
信号在产生之前,操作系统就已经内置了所有信号的识别和处理方式。(通过宏定义出得数字编号识别信号种类;在内核中存在一张handler表,即信号处理函数表。该表的数组下标对应信号编号,数组中存放的内容指向对应信号的处理方式)
并且信号产生时,操作系统可能在做更重要的事情,也就意味着信号产生之后不会被立即处理,需要被保存。在Linux中,每一个进程PCB中存在一张位图用来保存信号。位图中比特位的位置表示信号种类,比特位内容表示信号的有无!
进程是无法得知信号何时来的,也就意味着信号的到来和进程执行是异步的。
同理,在操作系统中已经提前内置了信号信息。我们通过kill -l
查看:
- 每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到。其中
1~31
为普通信号,43~64
为实时信号(不关心),没有32、33
号信号!这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明:man 7 signal
。
二、信号的产生
在Linux中,信号的产生方式分为四大类:键盘终端产生、系统调用产生、硬件异常产生、软件条件产生!
2.1 键盘终端产生
1)、终端信号产生过程
我们在终端输入诸如ctrl c、ctrl z、ctrl\
等指令,我们发现可以将进程终止退出,或者暂停。当我们输入这些指令后,对应硬件会向CPU发送光电信号通知CPU。此时CPU会将这些光电信号转化为中断号,在中断向量表中找到对应硬件的读取方法,将硬件数据读取到内存中。
当操作系统识别到这些特殊数据后,不会将数据拷贝给目标进程。而是转化为向目标进程发送信号,从而将目标进程退出或暂停等行为!
2)、操作系统得知硬件数据就绪手段
硬件和进程之间通常会伴随着数据拷贝。比如我们在终端输入指令信息,最后是需要将数据拷贝到内存中,然后看情况是否需要拷贝给目标进程的。当我们输入完指令回车确认后,意味着键盘资源就绪,等待操作系统将数据读走。但操作系统如何的知硬件资源就绪??
操作系统监测硬件资源就绪,有两种方式:
- ①:操作系统定期轮询检测所有硬件,判断那些硬件数据就绪。但这非常不现实,如何评判设定轮询间隔时间?时间太长,系统整体效率降低;时间太短会导致CPU疯狂进程监测,减少进程调度次数,进而影响程序性能!
- ②:另一种方式就是硬件就绪后,让硬件主动通知操作系统!
首先CPU周围会存在很多针脚,这些针脚对应唯一的一个编号,每一个针脚对应一个唯一硬件。当硬件资源就绪之后,硬件会向CPU发送光电信号。此时CPU检测到光电信号后,会将收到的光电信号转化为对应针脚的编号,并将该数字保存到寄存器中,我们也将这些编号称为中断号。
当OS识别到该寄存器中存在数据时,操作系统就知道存在硬件已经硬件资源就绪了,OS会立马停下手中的所有工作,将对应硬件中的数据加载到CPU中,完成数据的加载。
为了进一步提高读取硬件数据的效率,操作系统中存在一张函数指针数组,该数组中下标位置对应中断号种类,数组中保存了对应硬件的读取方法!我们将该函数指针数组称为中断向量表,该表会在操作系统初始化时生成!此时操作系统通过中断号即可索引到对应硬件的读取方法,快速将硬件数据读取到内存!!
当然CPU的针脚是有限的,硬件也不是直接和CPU链接。在CPU和硬件之间通信需要经过主板,而主板存在一定的扩展能力。并且并不是所有的硬件都有发送中断能力!
3)、 实例:前台进程、后台进程
下面我们创建一个process.cc
源文件,让其死循环输出信息。
#include <iostream>
#include <unistd.h>
int main()
{
int cnt = 0;
while(true)
{
std::cout << "running ..." << ++cnt << std::endl;
sleep(1);
}
return 0;
}
- 我们编译运行后,产生一个前台进程。我们可以在终端输入
ctrl c
发送2号信号来终止前台进程!
我们在键盘上按下ctrl c
后,会产生硬件中断。操作系统会识别到硬件数据就绪,此时操作系统读键盘上的数据,发送给目标进程。前台进程因为收到2号信号,进而引起信号退出!!
- 我们也可以通过
ctrl z
发送20号信号暂停前台进程!但由于前台进程不能被暂停,否则键盘将失效。此时当前被暂停的前台进程后转化为后台进程。shell外壳进程快速从后台切换为前台进程。
下面我们将前台进程输入重定向到log.txt
,死循环打印消息。然后ctrl z
发送20号信号,此时前台进程会变为后台进程。具体效果如下:
我们发现ctrl z
向目标进程发送20号信号后,前台进程变为后台进程,并且被暂停!
jobs
指令可以查看当前系统中的后台进程。bg 指令+ 后台进程编号
可以重新启动后台进程。fg 指令+ 后台进程编号
可以将后台进程提到前台,变为前台进程!- 前台进程只能有一个(键盘只有一个),后台进程可以有多个。两者本质区别在于前台进程可以接收用户输入,后台不行。
shell
进程比较特殊,不会被ctrl c
杀掉。并且根据具体情况,Os会自动将shell提到前台或后台!!
4)、验证终端按键是否产生信号
上述我们通过终端按键让进程产生一系列行为。当ctrl c
真的向目标进程发送了2号信号吗?ctrl z
真的向目标进程发送了20号信号吗?我们需要进一步验证!
操作系统提供了一个signal
系统调用即可,可以自定义捕捉信号。
#include <signal.h>
//函数原型如下,signal()第二个参数用于自定义捕捉信号
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
下面我们以自定义捕捉2号信号,分别通过终端ctrl c
和用户主动发送2号信号,对比进程行为!!
【源代码如下】:自定义捕捉2号信号,让进程受到2号信号退出时,打印一段消息!!
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void handler(int signo)
{
std::cout << "自定义捕捉信号: " << signo << std::endl;
exit(0);
}
int main()
{
std::cout << "pid: " << getpid() << std::endl;