信号(2)

递达-阻塞-未决

  • 实际执行信号的处理动作叫做信号递达(delivery)

信号处理方式

  1. 自定义
  2. 默认
  3. 忽略
  • 信号从产生到递达之间的状态叫做信号未决(pending)

本质上就是这个信号被暂存在task_struct 信号位图里面,未决
我先不知道咋搞,先保存着

  • 进程可以阻塞(block)某个信号

本质是OS允许进程暂时屏蔽指定的信号,
阻塞过程中

  1. 该信号依旧是未决的
  2. 该信号不会被递达,直到解除阻塞,才可以递达

忽略和阻塞有区别吗?

区别特别大,忽略是递达的一种方式
阻塞是没有被递达,一个独立状态,解除阻塞后可以被忽略、

信号表

在信号当中是有3张表格的
分别是handlerpendingblock

pending表:确认一个进程是否收到信号
handler表

void (*handler[32])(int):函数指针数组,里面放的就是一个一个的函数指针

  • pending :比特位的位置代表是哪一个信号,比特位位置的0,1代表是否收到了信号
    1代表收到了信号但处于未决状态(还未递达),0代表没收到信号,或者已经递达了

  • block表:本质上也是叫做位图结构,uint32_t block:(也叫做信号屏蔽字),比特位的位置代表信号的编号,比特位的内容代表信号是否被屏蔽阻塞,阻塞的信号无法被递达,

伪代码如下:

bool ishandler(int signo)
{
if(block&sig)
{
//根本就不看是否收到该型号
}
else
{
//该信号没有被block
if(signo&pending)
{
//说明没有被 blockl,而且已经收到了,可以调用这个型号了
handler_arr[signo](signo);//执行对应的回调方法
return true;
}
}


}

进程通过这三张表是可以识别信号的,
在这里插入图片描述

不要认为只有接口才可以算是system call 的接口
我们也要意识到,OS 也会给用户提供数据类型,

sigset_t

信号集,未决和阻塞标志可以使用相同的数据类型sigset_t 来存储,这个类型可以标识每个信号状态处于何种状态(阻塞还是未决),阻塞信号集也叫当前进程的信号屏蔽字,这里的屏蔽应该是阻塞而不是忽略

sig函数

int sigemptyset(sigset_t * set); 把所有的比特位全部置0,
int sigfillset(sigset_t * set)把所有的比特位全部置1
int sigaddset(sigset_t* set,int signo )把特定的信号加入到位图当中
int sigdelset(sigset_t *set,int signo)把特定的信号在位图当中去掉
int sigismember(const sigset_t *set,int signo)检查在位图当中是否有这个信号(成功放回1,失败放回0)

sigprocmask

统一修改的是block位图

int sigprocmask(int how,sigset* set,sigset* oset)
成功返回0,失败返回-1

set是一个输入型参数(把我们要的数据传进去),我们自己设置好的
oset是一个输出型参数(返回老的信号屏蔽字—block位图),没有修改之前的屏蔽字

how(修改的方法):

  • SIG_UNBLOCK:set里面包含了我们希望当前信号屏蔽字中解除的信号,相当于mask=mask&~set
  • SIG_BLOCK:set里面包含了我们希望添加到信号屏蔽字中的信号
  • SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,相当于mask=set

9号信号没有被屏蔽掉

#include<unistd.h>
#include<iostream>

#include<signal.h>
using namespace std;

int main()
{
    //虽然sigset_t  是一个位图结构,但是不同的OS 实现是不一样的,不能让用户使用直接修改该变量,
    //需要使用特定的函数,

    //这里的set是一个变量,所以这个set在哪里保存着呢
    //set是一个变量,也是在栈空间保存着
    //用户栈上保存(地址空间)    
    sigset_t iset,oset;//有了这个类型
    // set | =1;
    //外面必须使用set对应的接口函数
    sigemptyset(&iset);//清空
    sigemptyset(&oset);

    //外面想对2,5,6三个信号做屏蔽
    sigaddset(&iset,2);//把2号信号添加进去
    sigaddset(&iset,9);//把2号信号添加进去,9号信号无法被屏蔽掉的

    //设置当前信号的屏蔽字
    //获取当前进程老的屏蔽字
    // sigprocmask(SIG_SETMASK,&iset,&oset);//把2号信号给屏蔽掉了
    sigprocmask(SIG_UNBLOCK,&iset,&oset);//把2号信号给屏蔽掉了
    sigprocmask(SIG_BLOCK,&iset,&oset);//把2号信号给屏蔽掉了
    //ctrl c 给屏蔽掉了,不会被递达的
     
    while(1)
    {
        cout<<"hello"<<endl;
    }

    return 0;
}

sigpending

int sigpending(set_t *set)
,输出型参数
不对pending位图进行修改,而只是单纯的获得pending位图
(类似waitpid里的status)
pendind位图不是让我们修改的,而是OS,我们只需要获取就可以了

如果我们的进程先屏蔽掉2号信号,再不断的获取当前进程的pending表,然后手动发送2号信号,因为2号信号不会被递达,所以,不会的获取当前进程的pending位图,打印出(00000010),发送后不能被递达就会永远保留再pending位图里面

signal就是修改handler表

#include<unistd.h>
#include<iostream>

#include<signal.h>
using namespace std;



void showpend(sigset_t& pending)
{
    //检测一下信号是否存在在里面
    int i=0;
    for(i=1;i<32;i++)
    {
        if(sigismember(&pending,i))//成功放回1,失败放回0,
        {
            //检查信号是否存在pending位图里面
            cout<<"1";
        }
        else
        {
            cout<<"0";
        }
    }
    cout<<endl;
}

void handler(int signo)
{
    cout<<"2号信号被递达了,已经完成了处理 "<<signo<<endl;
}
int main()
{
    //虽然sigset_t  是一个位图结构,但是不同的OS 实现是不一样的,不能让用户使用直接修改该变量,
    //需要使用特定的函数,

    //这里的set是一个变量,所以这个set在哪里保存着呢
    //set是一个变量,也是在栈空间保存着
    //用户栈上保存(地址空间)  


    signal(2,handler);//捕捉一下2号信号  
    sigset_t iset,oset;//有了这个类型
    // set | =1;
    //外面必须使用set对应的接口函数
    sigemptyset(&iset);//清空
    sigemptyset(&oset);

    //外面想对2,5,6三个信号做屏蔽
    sigaddset(&iset,2);//把2号信号添加进去
    // sigaddset(&iset,9);//把2号信号添加进去,9号信号无法被屏蔽掉的

    //设置当前信号的屏蔽字
    //获取当前进程老的屏蔽字
    sigprocmask(SIG_SETMASK,&iset,&oset);//把2号信号给屏蔽掉了
    // sigprocmask(SIG_UNBLOCK,&iset,&oset);//把2号信号给解除屏蔽了
    // sigprocmask(SIG_BLOCK,&iset,&oset);//把2号信号给屏蔽掉了
    //ctrl c 给屏蔽掉了,不会被递达的
    sigset_t pending;
    int cnt=0;
    while(1)
    {
        
        sigemptyset(&pending);//把他里面的数据都给他清空一下
        sleep(1);
        sigpending(&pending);//获取了pending位图
        showpend(pending);
        cout<<"hello"<<endl;
        cnt++;
        if(cnt==10)
        {
            //把2号信号解除屏蔽
            //老的屏蔽字没有对2号进行屏蔽
            //在这之前2号信号都已经被屏蔽掉了,所以前面用ctrl c是没什么用的
            sigprocmask(SIG_SETMASK,&oset,NULL);//在这里之后才可以有用的
            cout<<"恢复对2号屏蔽字的使用,可以被递达了"<<endl;
            //被递达了之后,又会,我们无法看到恢复屏蔽,2号信号的默认动作是终止进程,所以看不到现象
        }
    }

    return 0;
}

信号发送后

信号是在合适的时候处理,因为信号的产生是异步的,也就意味着当前进程可能正在做更重要的事情,
信号可以延时处理(取决于OS和进程)

什么是”合适“的时候,因为信号是保存在进程的PCB中,pending位图里面,检测,处理(检测,忽略,自定义)
当进程从内核态放回到用户态的时候,进行上面的检测并处理工作

  • 内核态:执行OS 的代码和数据时候,计算机所处于的状态就叫做内核态,OS的代码的执行全部都是在内核态
  • 用户态:用户代码和数据被访问或者执行的时候,所处于的状态,我们自己写的代码全部都是在用户态中执行的,

主要是权限大小的区别

我们很经常在用户态调到内核态,再从内核态转移到用户态:系统调用(open),从普通用户变成了一个
用户调用系统函数的时候,除了进入函数,还有身份的变化,从用户的身份变成内核的身份。

在这里插入图片描述
死循环:也有可能进入内核态,因为死循环太久了,就被OS 给回收了,这个之后就执行了进程替换,一定会有用户到内核,内核到用户
信号的处理流程

在这里插入图片描述

在这里插入图片描述

为何一定要切换成为用户态,才能执行信号捕捉方法,
OS 是可以执行用户的代码,但是OS 不相信任何人,轻易不执行别人的代码,身份特殊,不能直接执行用户的代码
(用户做了不合理的动作,那OS 也能搞它的代码,很危险)

sigaction

和signal差不多
修改的是handler函数指针数组(handler表)
信号捕捉,注册信号,对特定的信号进行处理

#include<signal.h>
int sigaction(int signo,const struct sigaction* act,struct sigaction* oact)

act 是一个输入型参数(把动作方法填到这里面)
oact是一个输出型参数(带回老的信号,不想要的设置位nullptr)

struct sigaction {
void (*sa_handler)(int);//方法
void (*sa_sigaction)(int, siginfo_t *, void *);//实时信号
sigset_t sa_mask;
int sa_flags;//选项
void (*sa_restorer)(void);//实时信号
};

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<cstring>
using namespace std;



void handler(int signo)
{
    //信号捕捉动作
    cout<<"signo="<<signo<<endl;
}

int main()
{
    struct sigaction act;//定义了一个sigaction类型
    memset(&act,0,sizeof(act));
    act.sa_handler=handler;
    sigaction(2,&act,nullptr);//对2号信号进行注册了
    //本质是修改当前进程的handler函数指针数组特定的内容
    while(true)
    {
        cout<<"hello "<<endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述
信号捕捉特性

  1. 当我们处理某一个函数调用的时候,首先要把要执行的信号先加到屏蔽字里面,当信号处理函数返回的时候就自动恢复原来的信号屏蔽字,这样就保证了再处理信号时,如果这种信号再次产生,它就会被阻塞到处理完为止。(如果我们收到一个信号但是不屏蔽它,再信号处理函数内部,再收到信号,handler方法就会被不断的被调用,处理完才能处理下一个
  2. 如果再调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外的一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时,会自动恢复

volatile

gcc volatile.c -O3( 编译器优化 )

#include<stdio.h>
#include<signal.h>
volatile int flag=0;//内存里面读取flag,来检测flag


void handler(int signo)
{
    flag=1;
    printf("change flag 0 to 1\n");
}
int main()
{
    signal(2,handler);
    while(!flag);//发送2号信号之后就会停止循环
    printf("这个进程是正常退出的\n");
    return 0;
}

编译器优化之后,直接把flag放到了cpu寄存器上面,不在内存里面了
但是在signal调用函数的时候修改了flag,这个是对内存当中的flag修改
cpu检测都不是内存,所以我们无论怎么ctrl c都不会退出
cpu访问内存就被屏蔽掉了

而我们加入volatile之后,
作用:告诉编译器,不要对我这个变量做任何优化,读取必须是贯穿模式的从内存到cpu,不要读取中间缓冲区寄存器中的数据,永远都能看到内存,
保持内存的可见性

在这里插入图片描述

  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevin~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值