信号产生与处理


信号概念

生活中充斥着各种各样的信号,如一道广播响起、闹钟响起、肚子呱呱叫、红黄绿、一个人说话、手机软件通知、快递短信等等都是一些信号;而这些信号既然出现了就不是不做出回应的,有人要对这些信号进行处理,在校生当上课铃声响起之后,学生要考虑要不要去上课,或者先不去先上此时或者还在路上,还有就是肚子呱呱叫表征饿了,那么可以立即去吃或者过一段时间再去吃,那么过一段时间再去执行这个信号你得记住这个信号,没有立即执行可能是由于你正在忙自己的事情很重要,若是不将信号保存起来等到你想执行的时候又找不到信号了,就比如周末没课,学一周学累了,在寝室玩游戏放松一下,但是在玩游戏之前点了一份外卖,然后就开和室友打游戏了,过了一段时间当外卖送到他会给你发送消息,通知你去取,当时你正在有效焦灼阶段你就先不去取,不过你记在脑海中已经记住了这个信号当你打完这一把游戏就会去取回来,当过了10多分钟,游戏打完了你就想起了你有外卖到楼下了你就去拿回来,这样及得到了放松又得到了饭吃!

在这个示例中外卖员发送短信为信号产生,当你在打游戏没有来得及处理这个信号不过你已经将它记下为信号保存,当游戏打完想起有外卖到了去取为信号处理。信号产生之后可能不会立即处理因为有自己更重要的事情在做,然后要将其保存下来在一个时间窗口保存着!

我们是如何识别信号的?认识信号是因为知道这个信号,记住了,就是即使在一个阶段就算是没有信号产生但是将来对于这个信号要产生的动作是记住了的,就算是信号产生之后也知道如何处理信号,有明确的认知就如上课铃声,就算周末没响但是当周一到周五铃声响起之后同学们都会知道并且还能作出反应。在计算机中每一个进程对于各种信号都是认识的,只是没有收到信号的时候不会做什么一旦收到信号之后就会做出一系列动作来回应他收到的信号。

进程必须具备识别+能过处理信号的能力,就算是信号没有产生,也要具备处理信号的能力而这样的功能是属于进城内置功能的一部分。然后当进程真的收到了一个信号之后虽然可能不会立即处理但是等到合适的时候终究是要处理这一个信号的。而一个进程在信号产生到信号处理必须有一个时间窗口用来临时保存那些收到的信号。

信号产生与处理

运行一个进程,在键盘上按组合键ctrl+c就能让这个进程
给终止
在这里插入图片描述

那么为什么按下这个组合键会将正在运行的进程终止?这个进程一旦运行起来之后不再接受指令,ctrl+c能终止进程是这个组合键的按下转换为了给这个进程发送一个信号,而这个信号使进程终止!
前后台进程
进程一旦运行起来不接受指令:在LInux中,一次登录一般会配上一个bash,在每一登录只允许一个前台进程,却允许多个后台进程,当没有运行进程的时候bash就是前台进程,但是当运行一个进程之后bash就变成了后台进程,然后再给bash命令行发送指令是不会执行这个指令的,因为前台进程变成了正在运行的那个进程
在这里插入图片描述
再发送指令是不会执行的它会执行进程它本身的任务!

区分前后台进程:谁拿到键盘输入谁就是前台进程

若是不想让一个进程干扰bash进程可以将要运行的进程添加到后台进程,使用取地址符&将进程添加到后台中运行
当将这个进程添加到后台中运行之后再从键盘中输入就会执行对应指令但是按ctrl+c组合键并不会终止进程,因为它是后台进程不会接收来自键盘的输入,只有向这个进程发送9号信号杀死它!
ctrl+c本质是进程解释为收到了信号,收到一个2号信号
在这里插入图片描述
而进程收到2号信号默认动作就是让进程终止。
系统存在各种信号,Kill -l查看信号
在这里插入图片描述
其中1-31号信号为普通信号,34-64为即时信号(就是当收到信号之后就要放下手头工作去处理收到的信号)。
那么普通信号是不是可以不立即处理?是的。普通信号可以先进性保存等到合适时候再处理。而对于收到的信号处理方式有三种

信号处理

1.默认动作:就是这个信号本身应该处理的动作
2.忽略:对这个信号视而不见,不处理
3.自定义动作:对于这个信号,我有自己的一套处理方式,不受制于别人,如有些同学一旦听到上课铃声就跑去上厕所,原本应该在教室好好上课的,他反而自定义了动作去上厕所。

而对于自定义动作有一个函数设置,signal函数对收到的信号做自定义动作,当收到信号之后就去调用handler函数
在这里插入图片描述
参数

第一个参数为收到的信号编号
第二个参数为一个函数指针类型—>函数,当收到信号之后就调用这个函数,执行函数中自定义动作
修改特定进程对于信号的处理动作

三种对于信号的处理方式,每一个进程只能选择一种处理方式。

#include<iostream>
#include<cstdlib>
#include<unistd.h>
#include<signal.h>
#include<string>
#include<sys/types.h>

using namespace std;
void myhandler(int signo)
{
    cout<<"process get a signal :"<<signo<<endl;
}


int main()
{
    signal(2,myhandler);
    while(true)
    {
        cout<<"I am a process,pid:"<<getpid()<<endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

当收到从键盘中按下ctrl+c组合键之后进程没有终止,而是去执行打印动作去了,这样也证明了键盘组合键是给进程发送了信号。并且他以前的默认动作被覆盖之后就不会终止进程,所有要想让这个进程退出就只有将它杀掉。这个signal对于自定义动作只设置一次,后续都有效果,而handler只有在收到信号之后才会被调用,就像是这个进程已经知道只要收到2号信号就会取调用handler执行他其中的动作,就如闹钟设置,闹钟设置但是不立即响只有达到触发条件之后才会响。

singnal函数对于信号自定义可以说是对信号的捕捉,不过对于有一些信号是不能捕捉的,19号信号和9号信号不能捕捉。

循环捕捉

int main()
{
    
    for(int i = 1; i <= 31; i++)
    {
        signal(i, myhandler);
    }

    while(true)
    {
        cout << "I am a crazy process : " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

只有9号信号和19号信号不能被捕捉,因为若是这两个信号都能被捕捉,那么当有一天一个进程执行的任务出错,但是不论做什么都不能让它退出就会造成大量内存被占用知道有一天操作系统死翘翘带不动的时候电脑也就完蛋了,所有这两个信号不能被捕捉!

信号产生:键盘组合键、kill命令、kill接口(系统调用) 还有产生信号方式:raise,给当前进程发送指定的信号,自己给自己发信号

在这里插入图片描述
他就是相当于调用kill接口!
函数成功返回0,错误返回-1
产生信号之-abort
abort函数使当前进程接收到信号而异常终止。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
这个进程收到了信号导致异常终止!它是收到了6号信号
在这里插入图片描述
信号产生方式无论有多少种,后面都是由操作系统发送给进程,执行者是操作系统。
进程异常
一个进程异常本质是进程收到了信号
证明一个进程异常是收到信号,用signal捕捉
模拟一个除零错误
除零异常

int main()
{
    //signal()
    int a = 10;
    int b = 0;

    a = a/b;

    cout<<a<<endl;
    return 0;
}

在这里插入图片描述
除零错误是否是向进程发送了8号信号?用signal捕捉一下,除了9号和19号都是可以捕捉的所有可以验证。
使用信号捕捉之后它会一直执行异常而引发的动作
在这里插入图片描述
为什么异常捕捉之后,会一直调用这个函数执行自定义动作呢?这个异常为什么一直被触发?异常是进程收到了信号,并且一个进程异常默认动作是会退出进程
man 7 signal查看信号详细信息
在这里插入图片描述
要确认异常是收到信号只需要将其捕捉即可!但是从捕捉结果来看出异常捕捉之后进程不退出进程一直不退出,也就代表信号一直被捕捉,就是信号一直被触发,那为什么这个信号要一直被触发?
为什么进程出异常了操作系统会给进程发送信号?

因为进程出异常引起了系统问题,所有操作系统会给进程发送信号然后告诉进程让他终止或执行这个信号编号默认的动作。所有信号无论有多少种产生方式都是由操作系统发送给进程!

那么操作系统为什么要检测异常?操作系统怎么知道的?

在执行一个进程代码时,CPU从上往下执行代码,CPU通过它内部一个程序计数器(eip/pc寄存器)指针可以知道执行到哪一行代码了,然后在CPU内部还有一个状态寄存器表征代码是否出异常,状态寄存器中有一个自己的标志位(比特位级别)溢出标志位当进程出现除零错误的时候这个溢出标志位就会由0变为1,所有当哪一行代码出除零异常之后,CPU内部的状态寄存器就会获取到,然后反馈给操作系统,操作系统就会知道,那么一个进程的异常是不会影响到整个操作系统的,当进程退出的时候会将自己的上下文信息一并带走,整个进程的CPU数据都是进程的上下文,所有当出异常操作系统发送信号通知进程之后,进程退出之后,这个异常就是被带走所有不会影响到操作系统,虽然修改的是CPU内部的状态寄存器但是受影响的只有进程自己

任何异常只会影响进程本身,不会波及操作系统,引起错误的只是进程的问题,当CPU内部出错溢出,操作系统需要知道,因为操作系统是软硬件资源的管理者并且CPU是硬件,所有他才需要知道

野指针异常!

int main()
{
    char *p = nullptr;
    *p = 0;
    return 0;
}

在这里插入图片描述
这也实际上是一操作系统向进程发送信号

野指针异常是因为进程的虚拟地址到物理地址转换失败,在进程的进程地址空间中,虚拟地址到物理地址之间的映射存在的页表,这一个页表的映射是由一个内存管理单元(MMU)来完成查找页表映射,而这个MMU是集成在CPU内部,当地址关系转换失败MMU会报错,然后在CPU内部有一个寄存器会将转换失败的地址放入这个寄存器中,所有这个寄存器表征着失败,操作系统就会给进程发送信号,进程收到信号之后就会执行终止动作。进程出异常了,操作系统是怎么知道出的哪一种异常,在CPU内部存在各种寄存器,各种异常对应不同寄存器,当进程出异常之后对应寄存器就会出现错误,并由操作系统检测得知然后给进程发送信号。
进程出异常是不会影响别的软硬件的资源的,因为它存在上下文!

所有进程出异常操作系统给进程发送信号之后,这个信号被捕捉从而执行自定义动作,进程不退出,所有这个出异常进程的上下文一直在CPU中运行,然后这个异常又是一直存在的,CPU内部对应的寄存器就会一直报错,然后操作系统在管理软硬件资源时,检测到CPU内部寄存器出现错误就会一直给进程发送信号,然后进程收到信号又不终止而是去执行自定义动作,所有这个信号才会被一直触发!

操作系统给进程发送信号之后被捕捉,捕捉信号是提供一种渠道,使用户知道怎么错的,但是就是改不了,虽然操作系统能改,或者当一个进程触发了软硬件资源错误,操作系统也只是给进程发送信号而不是直接把这个进程给杀掉,是因为进程是由用户启的,可能某一个进程在做一件很重要的事情,但是不小心出触发了一个错误,操作系统不能说我来把这个进程干掉,若是干掉之后当有一天创建这个进程的用户来查看进程对于任务的完成情况却是发现进程已经不存在了就会问责操作系统,操作系统也是由人写的,一个操作系统要好用必须要兼顾各种方面所有当一个进程出现异常操作系统也只是给进程发送信号!

软件产生异常

SIGPIPE是一种由软件条件产生的信号,管道文件,读写端一端关闭一端正常是软件异常。当读取软件资源不就绪也会产生异常。
软件条件产生异常alarm函数,alarm函数的作用是设置一个定时器,在seconds秒之后,将会发送SIGALRM信号给当前的进程。
在这里插入图片描述

设置一个5秒后的闹钟

int main()
{
    int n = alarm(5);

    //int i = 10;
    while(1)
    {
        cout<<"I am process ,pid:"<<getpid()<<endl;
        sleep(1);
        //i--;
    }

    return 0;
}

在这里插入图片描述
如果不对SIGALRM信号进行忽略或者捕捉,默认情况下会退出进程。设置闹钟起始是给进程发送一个14号信号,对其进行捕捉
设置闹钟触发一次之后就不会再响了并且闹钟原本响过之后就要终止进程但是在这里被捕捉之后,就覆盖了它的默认动作就不会再退出进程
在这里插入图片描述
若是要连续触发只需要在自定义函数中再设置闹钟

触发之后alarm的返回值,是它触发之后他还剩下多少时间离原本计划应该响的时间。它返回值都是上一个闹钟还剩下多少时间(距离既定时间)

用kill命令发送信号,让闹钟提前触发,查看alarm返回值

在这里插入图片描述

闹钟只会触发一次,他不是异常,他只是一个软件条件,满足这个条件就会触发闹钟!

信号发送

对于进程而言自己有没有收到信号?收到哪一个信号它是怎么知道的?

操作系统给进程发信号,是给进程的哪一部分发送的,光是给进程发送的话太过于抽象了,因为进程包含太多的东西了,错做系统给进程发送的信号是给进程的PCB发送的,task_struct发送,在task_struct中维护了一个字段,整数字段,,这个整数当作32个比特位来使用,然后0号下标不用,每一个下标当作一个信号编号,所有当操作系统给进程发送信号进程收到信号之后,task_struct中signal字段对应比特位的内容由0变为1就可以了!

在这里插入图片描述

用比特位表示收到的是哪一个信号,然后对应比特位上的内容表示有没有收到信号
这个signal的含义

1.比特位的内容是0还是1,表明是否收到信号
2.比特位的位置(第几个),表明信号的编号
3.所谓的发送信号本质是操作系统去修改task_struct信号位图对应的比特位,由0写为1,写信号信号发送就是将比特位由0置为1

为什么是操作系统向进PCB发送信号?因为操作系统是进程的管理者,只有他又资格去修改进程PCB内部的数据。

  • 14
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值