信号:产生--保存--处理

理论:

信号和信号量没有任何关系

信号是OS提供,让用户(进程)给其他进程发送异步信息的一种方式
1、在信号还没有发生的时候,进程已经知道了当发生是要干什么事了(已经给进程大脑植入了相应信号发生时的处理方式了)
2、信号到来的时候,如果进程有更重要的事的话,可以暂时不处理信号,但是必须把信号保存起来。到合适的时候再做处理
3、信号产生是随时的,进程无法准确预料,所以信号是异步发送的(在信号产生之前。进程一直在忙自己的事情)

信号运行期间图:
在这里插入图片描述

准备

1、常见的信号

2、信号处理的方式

1.默认的方式
发送的信号让进程执行,系统默认的每个信号的功能

2、信号捕捉,自定义信号,把某个信号通过系统调用signal(),改变此种信号
在这里插入图片描述
3、忽略信号

信号的产生

1、系统调用:
①、kill(进程pid,所要发送信号的宏/数)
在这里插入图片描述
用kill自定义一个信号的命令使用程序:
在这里插入图片描述
②、raise(所要发送信号的宏/数)对进程自己发送 相当于kill(getpid(),所要发送信号的宏/数)
在这里插入图片描述
③、abort()终止信息
对自己发送信号6) SIGABRT 相当于kill(getpid(),6)

总结:可以看出所有的·系统调用都是调用的kill

2、kill命令
在命令提示后面直接输入kill -信号数 -pid 就可以向某个进程发送信号

命令的本质就是代码写得程序,又因为信号只有OS可以发,所以kill命令的代码中一定是调用了系统调用kill()

3、软件条件
管道pipe
当读段关闭后,OS就会发送信号给写段,把写端也关闭。
闹钟:
alarm
在这里插入图片描述
在这里插入图片描述
4、进程异常,
当进程发生异常的时候,OS就会给进程发生信号终止进程
在这里插入图片描述
5、键盘产生:
ctrl+c
ctrl+

对信号产生的底层理解:

kill 命令,系统调用,都是调用系统接口,从而驱使操作系统去完成信号发送的。

但是键盘生成又是怎么办到的呢?
首先: 操作系统如何知道键盘在输入的呢:

硬件中断技术:CUP壳外有很多的针头:每个指针头链接着一些硬件,当相应的硬件发生操作的时候,针头就会产生高电压,CUP根据哪一个整体针头就可以识别到是哪一种硬件,然后把相应硬件用一个数字代替,然后高速操作系统,操作系统通过拿到CPU中保存的这个数字当做数组下标到中断向量表(数组指针)中找到相应的指针指向的可以读取键盘输入内容的结构体,完成键盘输入

键盘信号发送:
读取键盘的内容后,键盘内容都是字符,所以OS就要判定字符,如果字符是要输入到某个文件的内容的话,就会把字符放到相应文件的缓冲区中,
如果判定出来是命令的话,操作系统就要解释为相应的信号,发送给进程

信号是保存在进程PCB的一个整形常量中的,通过位图的方式,1就是存在,0就是不存在,所以我们的信号只有32个
在这里插入图片描述

总结:可以看出信号是发送到进程PCB里面的,所以不管以任何方式发送的
信息,想要该表PCB这样的内核数据结构,就必须要有OS提供系统调用。

所以异常,(除零操作或者野指针)的发送信号也然不开操作系统:
在这里插入图片描述
补充:现在我们已经知道了信号,那么之前在学习进程控制的时候父进程在等待子进程退出的时候有一个接收值,这个值也是位图的形式,
在这里插入图片描述
在这里插入图片描述

信号的保存

并不是信号发送被接收后信号就会执行,信号的接收只能算做信号的保存,PCB中不仅有保存信号的pending位图,还有阻塞信号的block位图
在这里插入图片描述
三者之间的关系:
在这里插入图片描述
三张表匹配的系统调用:
在这里插入图片描述
用发送2号信号,来模拟一遍信号被阻塞,到信号被解除阻塞,pending中值的变化。

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cassert>
#include <sys/wait.h>

void PrintSig(sigset_t &pending)
{
    std::cout << "Pending bitmap: ";
    for (int signo = 31; signo > 0; signo--)
    {
        if (sigismember(&pending, signo))//判断信号是否在pending中的系统调用
        {
            std::cout << "1";
        }
        else
        {
            std::cout << "0";
        }
    }
    std::cout << std::endl;
}
void handler(int signo)
{
    // sigset_t pending;
    // sigemptyset(&pending);
    // int n = sigpending(&pending); // 我正在处理2号信号哦!!
    // assert(n == 0);

    // // 3. 打印pending位图中的收到的信号
    // std::cout << "递达中...: ";
    // PrintSig(pending); // 0: 递达之前,pending 2号已经被清0. 1: pending 2号被清0一定是递达之后
    std::cout << signo << " 号信号被递达处理..." << std::endl;
}


int main()
{
    // 对2号信号进行自定义捕捉 --- 不让进程因为2号信号而终止
    signal(2, handler);
    //1、屏蔽2号信号
    //1.1:
    //首先定义一个sigset_t 型的变量,来把屏蔽了2号信号时,block集中的值先弄出来,
    //这一步还并没有到系统中屏蔽2号
    sigset_t block,oblock;//一个用来更新,一个用来获取老的
    //先清空
    sigemptyset(&block);
    sigemptyset(&oblock);
    sigaddset(&block, 2); // SIGINT --- 根本就没有设置进当前进程的PCB block位图中
    //1.2真正去屏蔽2号信号
    int n = sigprocmask(SIG_SETMASK, &block, &oblock);
    assert(n == 0);
    // (void)n; // 骗过编译器,不要告警,因为我们后面用了n,不光光是定义
    std::cout << "block 2 signal success" << std::endl;
    std::cout << "pid: " << getpid() << std::endl;

    //观察pending图
    int cnt=0;
    while(true)
    {
        //2、获取pending图
         sigset_t pending;//定义一个变量
        sigemptyset(&pending);//清空
        n = sigpending(&pending);//把pending图获取到pending变量中
        assert(n == 0);


        //3、打印pending位图
        PrintSig(pending);
        cnt++;

        //打印20次后解除对2号信号的屏蔽
        if(cnt==20)
        {
            std::cout << "解除对2号信号的屏蔽" << std::endl;
            n = sigprocmask(SIG_UNBLOCK, &block, &oblock); // 2号信号会被立即递达, 默认处理是终止进程
            assert(n == 0);
        }
        sleep(1);


    }
}

结果
在这里插入图片描述

细节:是先清0,再递达的。

信号的处理

之前我们一直说信号会在合适的时候处理信号,
合适的时候:进程从内核态切换为用户态的时候信号会被检测处理
进程从内核态准备回用户态的时候就回检测信号做处理
比如是异常,那么进程在内核处理完异常后,准备回用户态的时候就会处理信号。
什么是内核态,什么是用户态呢?
之前在说地址空间的时候一直有一部分【3,4】G的内核空间没有说,::
在这里插入图片描述
信号捕捉的处理一共有四次的状态切换
在这里插入图片描述
知识补充,操作系统是如何运行起来的呢?
其实操作系统是很懒的,他也要有人不断地督促他他才会执行操作
硬件中断:
其实信号技术就是对硬件中断的软件模拟
在这里插入图片描述
所以操作系统能运行起来是因为有不断的硬件中断,即使没有外设的硬件中断,CUP也会自己产生中断来促使操作系统进行进程调度:
在这里插入图片描述

扩展知识:

另一个捕捉信号的系统调用:sigaction

当某个信号的处理函数被调用(正在进行次信号的处理),该信号会被自动加入信号的屏蔽字段,处理完成后又从屏蔽字段中移除。
这样做的原因是:当这个信号正在被处理的时候,来了很多相同的这个信号,那么
这些信号就只会在保存字段中不断刷新保存1,并不会被嵌套的再去执行这个信号的处理函数。防止这个相同的函数被调用很多,造成栈溢出。

如果我们想在调用信处理函数的时候,不仅当前信号被屏蔽,也想屏蔽其信号的话

我们可以使用另外一个信号捕捉的系统调用:
在这里插入图片描述

可重入函数

一个函数是否可以被同时调用:
在这里插入图片描述

大多数的函数都是不可重入函数

volatile关键字

我们用信号的方式来理解他:
在这里插入图片描述

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值