目录
一、什么是信号
举例子:
进程如何认识信号
信号与进程的异步
进程如何储存信号
二、一个实例
signal函数:
三、实例后的思考
一个进程接受到信号后,处理信号的方法:
myhandler什么时候才会被调用
四、理解ctrl+c被解释成信号的过程
一、什么是信号
举例子:
举一些例子来理解:红绿灯、下课铃、倒计时、狼烟冲锋号、肚子叫、妈妈的脸色、语气、外卖电话……这些都是我们时候中常见的信号。
而且一般来说,我们看到信号就知道我们接下来应该要做什么了比如:红灯停、下课回家、饿了吃饭、外卖到了去拿等等等。
进程如何认识信号
一个进程在运行的时候也会接受到很多的信号,就像上面这些行为一样,收到了一个信号就会去执行一些默认的操作—————这写行为都是程序猿在编写os时就设置的默认操作,这也就是为什么进程会认识这些信号。就比如我像一个进程发送了一个终止信号,他就回去执行终止操作。
信号与进程的异步
给出一个场景,加深一下理解:你在家里玩游戏,你点了一个外卖。玩着玩着你接到了一个外卖电话,让你下去拿外卖,但是你在玩游戏,你说先放在那里,我等一下去拿。
对于进程来说这也是同理的,如果进程在执行一个优先级更高的任务的时候,接收到了信号是不会第一时间去处理的。
因此我们得出一个结论:信号的产生对于进程来说是异步的。
如果信号产生了,我们不第一时间处理的话,这就说明了,进程必须要有储存信号的能力。
进程信号时间线:
进程如何储存信号
进程是如何记录对应的产生的信号的呢?储存在哪里?
先看一下常用的对进程信号吧:
观察一下 ,一共有多少个信号呢?许多同学会脱口而出64个,其实不然,31到34之间被分开了。因此这62个信号被分成了两个部分:1~31,34~64。
后面那部分我们不做讨论,那个是实时信号(接受到信号就马上要执行操作),我们的操作系统用不到。
我们会发现,1~31一共31个信号,非常巧合的只比int的比特位少了一个,int有四个字节,一个字节有八个比特位。聪明的同学已经猜到了存放信号的结构————位图。一个int整数每个比特位对应一个信号,0表示没有,1表示有这个信号。
这里也遵循了先描述在组织的原则,int组织。
那么这个int放在哪里的呢————task_struct结构体中。
其实通过上面的描述我们应该也感受出了,1~31号信号,我们只记录它是否产生。
所谓的发送信号,本质上是写入信号,谁写入——由操作系统写入,将0—>1。
二、一个实例
上面说了一大堆,如何证明我们的进程确实收到了信号呢?
先介绍一个函数,再通过一个代码实例:
signal函数:
原型:void (*signal(int signum, void (*handler)(int)))(int);
这里看不懂就跟着我简单了解一下就好了二,会用就行:
外面那一层void *其实就是声明了一个参数为int的函数指针。然后我们是可以直接调用这个函数指针的。然后里面那层看起来就非常正常了:
signal(int signum, void (*handler)(int))
这个函数有两个参数,一个就是信号,另外一个是我们自定义的一个函数的指针。
所以这个功能是什么呢:
简单说就是设立一个规则,就是当我们的进程接收到了和第一个参数相等的信号时,就会调用后面我们写的函数指针。
mykill.cc
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cerrno>
#include<cassert>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
void myhandler(int signo)
{
std::cout<<"get a signal:"<<signo<<std::endl;//打印获得的信号
}
int main()
{
signal(SIGINT,myhandler);
while(true)
{
std::cout<<"我是一个进程,我正在运行"<<getpid()<<std::endl;
sleep(1);
}
return 0;
}
然后我那这个函数在linux终端下运行:
使用Ctrl+c的原因:用户按下Ctrl-C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程。
简单的说,就是:相当于操作系统给我这个进程写入了一个信号,这个信号就是2——SIGINT信号。
我们发现一般可以直接退出进程的操作无法退出,并继续执行。
我是通过Ctrl+\退出的,这个会被解释成3——SIGQUIT信号。
如果我调皮一点:把这个信号也自定义一下操作呢?
int main()
{
signal(2,myhandler);
signal(SIGQUIT,myhandler);//加一个
while(true)
{
std::cout<<"我是一个进程,我正在运行"<<getpid()<<std::endl;
sleep(1);
}
return 0;
}
我们会发现,ctrl+\也不行了。
我是通过命令行发送信号结束进程的:
直接将9信号写入18769进程。
三、实例后的思考
一个进程接受到信号后,处理信号的方法:
1、默认动作——就像ctrl+c就是默认退出前台进程。
2、忽略信号——进程可以选择忽略某些信号,这通常是由于进程对该信号无强制要求或者不能有效地处理该信号。使用 signal() 函数可以指定一个信号的处理方式为忽略。
3、用户自定义信号(捕捉信号)——就是用上面的signal函数实现的操作。
这上面三个是我们可以在上面的例子中发现的,还有另外两种情况:
4、阻塞信号:进程可以选择阻塞某些信号的传递,这样当该信号被发送给进程时,操作系统会将其挂起,直到进程取消该信号的阻塞。使用 sigprocmask() 函数可以设置信号掩码来实现信号的阻塞。
5、信号传递:进程可以使用 kill() 函数向其他进程或进程组发送信号,以实现进程间通信或通知其他进程。
myhandler什么时候才会被调用
是在调用singal函数的时候吗?
不是,它只是定下了这个规矩,接收到这个信号的时候才会调用这个函数。
四、理解ctrl+c被解释成信号的过程
这是一个键盘:
这是一个cpu,上面有很多个引脚 :
这些引脚连接不同的硬件,其中就好引脚连接了我们的键盘:
当键盘被摁下,cpu内部就会储存一个中断号,这里是9。
然后cpu就会从一个向量中断表(一个数组)里去寻找一个下标为9的元素:
然后这个元素就会指向一个读取键盘的方法:
这样就读取到了一个ctrl+c的数据,然后将2号信号写入对应的进程就可以了。