目录
一.进程信号的理解
1.1定义:
1.2举例:
1.3总结:
二.进程信号地使用:
2.1信号种类:
2.2而操作系统向进程发送信号地方式有四种:
2.2.1以键盘的方式向进程发送信号
接下来介绍一个系统调用函数signal():
2.2.2 以系统调用地方式向进程发送信号——kill函数
abort():
raise():
2.2.3硬件异常产生相应的信号
论证:a/=0语句的错误是否来自于OS发送给进程的SIGFPE8号信号:
总结:
2.2.4.软件条件的产生
alarm();
三.总结
Linux的信号是可执行程序在被CPU运行计算的过程中,发生了某些异常原因导致进程被迫中断的方式。
在现实生活中,我们有也可以处处见到信号:
1.过马路的红绿灯
2.微信的消息提醒
3.手机的来电铃声
4.闹钟声响起
5.母亲叫孩子起床
以上这些例子全是信号的代表。
我拿第一个例子:过马路说吧:
在过十字路口时,总是会有红绿灯,这是交通规则,管制着人车出行的安全。而我们能够识别出“红灯停绿灯行,黄灯亮了等一等”——这是第一点,因为老师或者父母教育过你,我们的大脑记住了对应的红灯、绿灯、黄灯的属性和需要做出的行为;绿灯亮了,表示我们可以过马路了,但是我不一定要立刻过这个马路!我可能正处于和朋友分别的情况下,我正在和他说离别的话语——这是第二点,当信号到来时,我可能正做着更重要的事情,没有办法立刻处理这个信号;但绿灯亮的那个瞬间,我明白了一件事,我记住了要在绿灯的有限时间内穿过这个马路,我可能在和朋友说离别话的5秒后过马路,也有可能在10秒后过,总之我有一个时间上的概念——这是第三点,信号的到来,就必须记住这个信号,它需要被处理;最后,我有多种选择,选择a:我按照一般逻辑,绿灯亮了就和朋友分别过了马路(默认动作);选择2:我玩着手机过马路(自定义动作);选择3:绿灯亮了我并不过马路(忽略动作)
在这个例子中,共有4个重点:
1.人为什么能够识别出红绿灯的信号?——因为人们都认识它,对这些信号的产生会有相应的决策动作产生;
2.信号到来时,是否需要立刻处理? ——不一定要立刻处理,因为这时候你可能在做着比处理信号还重要的事情;
3.在第二点的基础上,信号到来的话,该怎么办?——需要先记住这个信号!
4.处理信号时,有几种方式?——a.默认动作、b.自定义动作、c.忽略动作
根据这4个重点,我们就可以很容易的了解到信号。
举一反三:
我中午点外卖,一个小时后,外卖小哥到了我家楼下并打了电话。根据第一重点,因为我之前点过外卖,知道了外卖员给我打电话就需要我下楼去取了;根据第二点,我接到了电话,回复了外卖员说:"我马上就会下去!" 但我此时正在打游戏,已经在打至关重要的团战了,走不开。所以我没办法下去取,只能等到我打完团战才能去处理这件事;根据第三点,外卖员给我打了电话,我脑子里已经记下了需要取外卖这件事情;根据第四点,我接到了电话,团战正好打完,我下楼去取(默认动作);我正在打团战,过了3分钟才下去取(自定义动作);我打游戏上头了,忘了这件事,导致外卖员走了并且我也没吃上午饭(忽略动作)
我小时候摔了一跤,哇哇大哭,被妈妈哄了半天没有用。妈妈不耐烦了,收起了慈爱的脸庞,用严厉的神情警告我说:“3、2、1!” 根据第一点,因为之前我也遇到过类似321的情况,但我没听话挨过好几次揍,我的大脑中死去的记忆突然又攻击了我~;根据第二点,我听到了妈妈说的3,2,1,我知道了妈妈生气了,要准备揍我了,但是我摔得太疼了,哭得停不下来,没办法安静;根据第三点,我记住了妈妈说得这句话,需要尽快安静下来;根据第四点,我听到妈妈说的话,停下来了,不哭了,免去一顿打(默认动作);我去找老爸求助,让老妈别打我(自定义动作);我头铁,老妈说了3,2,1 ,我还在哭,老妈又揍了我一顿(忽略动作)。
那么根据生活中例子,我们可以应用到Linux操作系统中去:
1.Linux的信号只能是由OS操作系统给进程发送,进程接收到信号所做的工作几乎都是中断,这是因为程序员写出的代码规则教给进程的,进程认识了各个信号,也就能做出相应的动作。
2.当进程收到了信号的时候,进程可能在执行着更重要的事情,该信号可能不会被马上进行处理。
3.进程本身一定是具有保持信号的能力,而保持的位置在PCB中。
4.进程对信号的到来,有三种对应方式:默认动作、自定义动作、忽略动作
对于第三点,一个信号发送给进程,进程会将信号进行保存,保存的位置在PCB中,证明:
在长时间学习Linux的过程中,我们已经在很多情况下默默见识过了信号,而常见的信号指令为kill指令,使用kill -l指令可以查看Linux操作系统中带有的所有信号列表:
如上:1-31是普通信号,34-64是实时信号。而之后我们常用的是1-31的信号。
1.以键盘的方式向进程发送信号
2.以系统调用地方式向进程发送信号——kill函数
3.硬件异常产生相应的信号
4.软件条件的产生
先来说第一点:
实验案例:
运行结果:
代码解析: 通过上图代码可知,采用while死循环一直打印一句话,之后我使用了ctrl+c键,终止了进程的执行。而ctrl+c键: 也相当于是一个信号,0S将其定义为2号信号。
SIGINT
进程中断信号
产生方式:键盘Ctrl+C
产生结果:只对当前前台进程,和他的所在的进程组的每个进程都发送 SIGINT 信号,之后这些进程会执行信号处理程序 再终止
验证方式:
除此之外,ctrl+\键也可以用于终止进程运行!
ctrl+\也是一种信号,对应kill指令中的第三信号:SIGQUIT
实验方式:
signal是拥有两个参数的函数,这个函数有些特殊
signum参数是想要捕捉的某个信号,可以填指定的信号数字,也可以填信号的名称;
handler参数是一个回调函数,在handler函数中可以将接收到信号做任意的处理。
该函数的作用:准备捕捉或屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数有handler给出
实验案例:
在代码中,signal意味着是一个信号受理的函数,如果进程没有接收到OS发送来的任何信号,那么signal函数就永远不会被调用!!!进程会跳过该函数往下执行。所以signal放在那里就相当于安保的作用,只要无人闹事,它就不会管制别人的行动,但是只要有人闹事,它就会出来镇场子。
运行结果:
在进程运行的过程中,我们只要一使用ctrl+c键——2号信号,就会被signal函数给捕捉到,捕捉到后会调用singal的第二参数回调函数,做出相应的动作。
注:signal函数并不能捕捉所有信号,它只能对指定的信号进行捕捉,例如,在代码中,signal函数的第一参数设置为了2,意味着signal函数只能捕捉2号信号,对于OS发送过来的其他类型信号,signal并不能进行捕捉。
kill函数/命令产生信号:
kill命令产生信号: kill -SIGKILL pid
kill函数: 给指定进程发送任意信号返回值:
成功:0;失败:-1 (ID非法,信号非法,设置errno)
参数:1.sig:中的任意一个发送给进程,表示你想要发送给某个进程的信号参数,可以选第1-第31个信号。
2.pid > 0:发送信号给指定的进程。(一般情况下都是这个)
pid = 0:发送信号给 与调用kill函数进程属于同一进程组的所有进程pid < 0:取 pid 发给对应进程组。
pid =-1:发送给进程有权限发送的系统中所有进程
实验案例:
代码解析:该进程用于一直打印一句话,死循环方式。
trans_test.cc:
代码解析:该进程的作用是向 signal_transfer进程使用指定的信号,进而中断signal_transfer进程的执行。里面使用了argv[ ]数组。
trans_test进程向signal_transfer进程使用了3号信号,上图可知3号信号为SIGQUIT中断信号。想要使用系统调用去给进程发送信号,必须得说明进程的文件名称,进程的Pid,以及想要发送的信号。
trans_test进程向signal_transfer进程使用了19号信号,该信号的作用是让进程暂停运行,并不会直接中断,我们可以对该进程使用18号信号,让该进程恢复运行:
该函数无参数,无返回值,用于中断自己进程的执行,该函数对应SIGABRT信号
实验案例:
代码解析:通过在死循环中,每一秒就输出一句话,cnt作为计数器,当cnt为5时,使用abort函数,给自己发送信号,中断自己的执行。
运行结果:
论证abort函数是否为SIGABRT信号:
该函数返回值为int,返回值为0代表调用成功;返回非0则代表调用失败。
实验案例:
代码解析:在死循环中,若cnt值为5,则调用raise函数,给自己进程发送3号信号,中断自己进程的执行。
运行结果:
注:
1.信号的处理行为的不同: 很多情况下,进程收到的大部分信号,都是进行进程中断的操作;
2.信号的意义:在我们学习的1-31号信号中,大多都是中断信号操作,但是信号的不同,所代表的意义也不同不同的信号代表不同的事件,就好比我们写代码,写好后运行的时候,会出现各种各样的编译错误比如参数有误类型不兼容等错误,它们最终都会让代码无法运行,虽然错误都会导致同一种结果(代码无法运行).但错误并不是同一种错误。
就好比:absort(); ===> kill(pid,SIGABRT);
raise(19) ; ====> kill (pid,SIGSTOP) ;
这两种函数的应用都可以被替换成kill函数去执行
通过结果可知:发现while循环只进行了一次就异常终止了,Floating point exception 表示浮点异常浮点异常表明是算术方面的异常。原因就在于:a/=0语句,该语句表示除零错误——即除数的值不能为0,否则会引发除零错误。
而除零错误代表的信号为SIGFPE8号信号!
运行结果:
我们发现结果一直是死循环状态,显示屏一直在捕获8号信号,大家千万不要以为这是循环的问题,千万不要以为是编译器一直在循环中一次又一次的执行a/a=0导致的8号信号的发送!大家可以试一试将a/=0语句放在循环前,也是一样的结果——显示屏会一直显示捕获到8号信号!
造成进程陷入死循环的原因在于:
左图紫色的这四个框是Cpu中的寄存器。当CPU执行代码时,如右图,进行算数运算,需要用到寄存器进行对数据的保存,例如eax中存放着变量a的数值,第二个寄存器中进行a/0,因为 a/=0 ==> a=a/0,将a/0的结果再赋值给a
而第三个寄存器则放着最终的结果。并且CPU中还有一个状态寄存器,状态寄存器会对寄存器进行的算数运算结果进行衡量,若无异常,则状态寄存器中的溢出标志位(默认为0)不会变,若算术过程中出现异常,则状态寄存器中的溢出标志位会从0变成1(溢出),出现溢出,会被CPU察觉,CPU会将其定义为运算异常并且告知OS操作系统,0S识别出状态寄存器的溢出标志位出现了异常,就知道CPU内部出错了继而知道了代码中哪步出现了错误。所以说第三个寄存器中保存的结果被状态寄存器发现异常所以其中的溢出标志位转换为1。
接受到信号,不一定会引起进程退出,有可能还会被调到CPU内部的寄存器只有一份,但是寄存器中的内容,属于当前进程的上下文!这时需要注意一个大问题,我们是否有能力或者动作去修正这个异常问题呢? 答案是:没有!所以出现了死循环的过程,当进程被切换的时候,就有无数次状态寄存器被保存和回复的过程。所以每一次恢复的时候,就让OS识别到了CPU内部的状态寄存器中的溢出标志位是1。
所以造成死循环最终的原因就是:CPU在运算的过程中遇到了除零错误,然后CPU向OS操作系统打报告,说:“我的状态寄存器标志位溢出了”,OS得知后根据CPU的报告找出了具体的错误,向进程发送了浮点数SIGFPE信号,在进程中,signal函数接收了SIGFPE信号,调用了它的第二参数,进行相应动作的语句输出。然后编译器返回到a/=0该语句中,发现除零错误仍然没被处理掉。于是CPU又向操作系统打报告,OS又向该进程发送SIGFPE信号......,就这样,CPU不停的报错误,OS不停的发送信号,signal函数不停的捕获该信号,但没有能力处理,就这样造成了死循环问题。
当程序在运行中产生了异常(除零,野指针等)导致进程终止,我的程序会触发操作系统内CPU或者内存管理单元的相关报错,进而被探作系统识别到,操作系统识别到会向我们的目标进程发送信号,我们的进程就自动会被终止,我可以选择不终止,选择捕捉异常,但没有什么意义,如上就算硬件异常产生的信号发送,不会调用系统接口,也不会自己主动去发送, 这是由软件行为自动触发,在操作系统内部自动工作;
异常也有不同原因的异常,收到不同的异常信号,就代表着不同的原因,根据进程收到异常信号的不同,我们可以反向的进行定位问题!不同的信号,代表不同的事件,可以根据信号,去标定进程出现的问题,进而去修改异常,让程序正常化!! !
所以这就是一般情况下为什么我们对进程使用各类信号后,进程接收到信号的反应几乎都是中断执行,强制退出的情况,因为我们没有能力去处理这些错误,只有从根源上(改正代码)去解决才行。
当初在学习管道的过程中,写代码: 两个进程通信个进程只负责读,当读进程关闭了读端,写进程立即一个进程只负责写,被终止继续写入,并退出。 写进程被立即终止退出的情况也是因为OS操作系统向该进程发送了信号,导致写进程退出。因为读端被关闭,写端无论写多少都没用,操作系统不允许资源浪费,所以立刻发出信号给写进程。
接下来,根据软件条件实现一个案例,让我们能够更加具体形象的看到软件条件产生的信号造成的进程终止:
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM(14号)信号,改信号的默认处理动作是终止该进程
alarm函数是设置一个计时器,在计时器超时的时候,产生SIGALRM信号.alarm也称为闹钟函数,注:一个进程只能有一个计时器时间如果不忽略或捕捉此信号,它的默认操作是终止调用该alarm函数的进程。
它的主要功能是设置信号传送闹钟。其主要功能用来设置信号SIGALRM在经过seconds指定的秒数后传送给目前的进程,如果在定时未完成的时间内再次调用了alarm函数,则后一次定时器设置将覆盖前面的设置,当seconds设置为0时,定时器将被取消。它返回上次定时器剩余时间,如果是第一次设置则返回0。
代码解析:通过采用死循环的方式一直运行,采用alarm函数的意义在于,让进程在执行代码一秒后直接调用alarm函数,让系统发送SIGALRM(14号信号)给该进程,然后该进程就会被终止。
运行结果:
当程序被运行后,进程会执行1秒钟的死循环,在这1秒钟内,CPU一直在cout输出cnt++的值,一直运行了68077次,因为alarm函数的参数为1,然后进程被动调用了该函数因为是CPU进行进程的代码执行,cpu速度很快,一秒钟就能实现数以万次的计算,而cout输出是外设进行的,外设的速度很慢,所以1秒内,cpu完成的万次计算想要让外设进行打印输出这么多内容需要好几秒才能完整打出来。
(14) SIGALRM
在POSIX兼容的平台上,SIGALRM是在定时器终止时发送给进程的信号。计算机程序通常使用SIGALRM作为长时间操作的超时信号,或提供一种隔一定时间间隔处理某些操作的方式。任意一个进程,都可以通过alarm系统调用在内核中设置闹钟,OS内可能会存在着很多的闹钟,那么操作系统要不要管理这些闹钟呢? ——先描述,再组织
案例2:
当进程在执行0.5秒后,alarm被调用,然后signal函数捕捉到了SIGALRM信号,调用Checksig函数,输出语句,并且继续执行alarm(1);又订了一秒后继续调用alarm,signal不停在0.5秒后的捕捉SIGALRM信号,捕捉到后,调用Checksig函数,再不停的预定1秒后继续预定闹钟,形成死循环。
于是会不停的调用Checksig函数,,alarm函数的作用也就相当于是sleep函数了,也是每隔一秒调用一次。
1.所有的信号都是由OS操作系统来发送给进程的,操作系统是所有进程的管理者,所以只有它有权利发送信号。
2.OS发送某一个信号是修改进程PCB的进程信号位图进行的。