漫话linux:信号的保存和处理

#哪个编程工具让你的工作效率翻倍?#

1.处于信号编号1到31的普通信号,多次产生只会记录一次,每一种普通信号都要有自己的存储方式,存储在数据结构中

typedef void(*handle_t)(int)    #处理方法

信号保存类似于硬件中断,一般由三个数据结构进行保存,两张位图(一张表存储默认(block记录是否屏蔽信号),一张表存储忽略(pending记录是否收到信号,记录阻塞),两张表控制函数指针数组去调用对应的函数),一个函数指针数组(handler中的比特位表示存储方法)

2.概念

        1.执行信号的处理操作叫信号递达

        2.信号从产生到递达之间的状态叫信号未决

        3.进程可以选择阻塞某个信号

        4.被阻塞的信号会一直保持信号未决状态,直到阻塞解除变为信号递达

        5.阻塞和忽略:与阻塞不同,忽略是信号递达后的操作,阻塞是未读,忽略是已读不回

3.信号集操作函数

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,是系统给用户提供的, sigset_t称为信号集,这个类型可以表示每个信号 的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。阻塞信号集也叫做当 前进程的信号屏蔽字,这里的“屏蔽”应该理解为(block层面的)阻塞而不是忽略

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统 实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做 任何解释,比如用printf直接打印sigset_t变量是没有意义的

#include <signal.h>
int sigemptyset(sigset_t *set);#清空
int sigfillset(sigset_t *set);#设置位图
int sigaddset (sigset_t *set, int signo);#添加特定
int sigdelset(sigset_t *set, int signo);#去掉特定信号
int sigismember(const sigset_t *set, int signo);#某型号是否存在

 sigprocmask读取或更改进程的信号屏蔽字(阻塞信号集)

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
//返回值:若成功则为0,若出错则为-1

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信 号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里, (保存就有意义:可以实现之后的交换,返回)然后 根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值(和|一样有1就为1)

sigaddset更改后通过mask传入,才能实现真正的屏蔽

   sigprocmask(SIG_SETMASK, &bset, &oset);
// 将自己的位图设置进block中,oset保存老的block位图
//我们已经把2好信号屏蔽了吗?ok

man 后检索的方法: /return val

sigpending

获取当前信号的pending信号集

#include <signal.h>
sigpending
//读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
#include<iostream>
#include<signal.h>
#include<unistd.h>
 
using namespace std;
 
#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31
 
 
void show_pending(const sigset_t& pending)
{
    for(int signo=MAX_SIGNUM;signo>=1;signo--)
    {
        if(sigismember(&pending,signo))
        {
            cout<<"1";
        }
        else
        {
            cout<<"0";
        }
    }
    cout<<endl;
}
 
int main()
{
    //1.先尝试屏蔽指定信号
    sigset_t block,oblock;
    //1.1初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    //1.2添加要屏蔽的信号
    sigaddset(&block,BLOCK_SIGNAL);
    //1.3开始屏蔽,,设置进内核(进程)
    sigprocmask(SIG_SETMASK,&block,&oblock);
 
    //2.遍历打印pending信号集
    sigset_t pending;
    while(true)
    {
        //2.1初始化
        sigemptyset(&pending);
        //2.2获取pending信号集
        sigpending(&pending);
        //打印
        show_pending(pending);
        sleep(1);
    }
    
    return 0;
}

 解除信号2屏蔽后,pending位图就没有信号2了

如果想一次屏蔽多次信号,可以使用vector

vector<int> sigarr={2,3};
for(auto& sig:sigarr) signal(sig,handler);

 9号和19号进程不可能被阻塞或忽略

4.信号的捕捉

当进程由内核态返回到用户态时,进行信号的检测和处理,代码运行三部分:自己写的+库+操作系统提供

调用系统调用:操作系统自动做身份切换,用户身份变成内核身份,或者反着来(不是只有系统调用才会陷入内核)

int 80从用户态陷入内核态的汇编语句

基于用户捕捉代码

只要是进程都是会被调度的,因为存在进程从 CPU 上时间片的剥离

6.地址空间

信号中间就会变为内核态

由于进程的独立性,有几个进程就会有几个页表,但是内核的页表只有一个(大约3到4GB),所以无论进程如何切换,都只能访问一个内核页表

进程视角:在自身的进程地址空间中调用系统中的方法

操作系统视角:任何一个时刻都有进程调度执行,想执行操作系统的代码,就可以随时执行

操作系统的本质:基于时钟中断的一个死循环内核可以查看到for(;;)pause()

计算机硬件中,有一个时钟芯片,每一个很短的时间都向计算机发送时钟中断

时钟督促 CPU ,将对应进程调度给 OS(硬件推着os(软件)走)

用户无法直接访问操作系统内核,为了安全起见,只有用户态才可以执行自定义信号方法

状态转化权限:ecs 切换3 0 (int 80:陷入内核进行切换),CR3存储状态

内核态:允许访问操作系统的代码和数据

用户态:只能访问用户自己的代码和数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值