信号保存、阻塞、处理


信号保存

信号产生的时候不会立即处理而是等到合适的时候在进行处理那么对于信号产生还没有处理的信号就要将其保存起来,信号从产生到递达之间的状态,称为信号未决(Pending),也就是信号保存。通过进程pcb中的位图结构保存,位图编号为信号编号位图编号对应内容代表是否收到信号!

信号处理

在计算机中信号处理这个动作称为信号递达(Delivery)。
对于一个进程来说,他处理进程的动作可以是这个信号原本处理动作的方法,进程也可以选择对信号忽略或者是自定义动作执行,处理信号的动作放入一张方法表中,当信号产生之后若是要执行信号就可以根据pending位图中的信号编号然后去查找对应方法表中的信号处理方法!在pcb中为每一个进程维护了一个handler表,它是一个函数指针数组,里面放的是函数的指针为每一个信号的处理方法,这样的信号处理方法默认是操作系统提供的,若是用户自己提供方法那么只需要把方法地址填入对应信号编号的数组中即可,通过系统调用signal实现自定义动作。一旦信号产生就可以根据表去找信号处理对应方法,这个和硬件中断很像,这个其实就是模拟硬件中断弄得,根据硬件中断来搞的。

信号阻塞

不过在处理信号之前,还要看这个信号有没有阻塞。进程可以选择阻塞某个信号,就是比如一位同学在学校上学,然后他对于某一个老师不喜欢,但是迫于学校威压又不得不去上课,不过这个老师本身管的并不严厉,所以对于这个老师安排的作业,你早就选择不做,并且这个老师也放任学生期末再交作业或者可以不交作业,你就选择了不作作业就选择让作业堆积起来让作业堆积起来不做,你就是把你作业阻塞住了,不过你是将其记住的,等到有一天你发现这个老师还不错的时候你就想起来做他的作业,这样你就把这个阻塞给解除。

所以进程可以选择阻塞 (Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

进程目前对于信号都会处理,但是若是进程选择将信号给阻塞(屏蔽)了话就会将信号保存起来就是一直存在penging表中,知道解除了对这个信号的屏蔽之后进程才可能会等到合适的时候处理这个信号,去handler表中查找方法执行。信号没有产生可以屏蔽吗?对信号屏蔽只是一种状态,就是一种选择,屏蔽还是不屏蔽而已所以信号没有产生是可以对信号做屏蔽的就是如老师布置作业反正你有一个理念不管这个老师布置或者不布置作业我都选择屏蔽它,这个原理是一样的,不管信号产生与否,都会将其屏蔽!屏蔽信号也是对一张表做操作,block表,它是和pending表对应也是一个位图结构,比特位编号是信号编号,比特位的内容为对信号的屏蔽或者不屏蔽,0表示不屏蔽;1表示屏蔽,pinding和block在物理结构上是一样的只是它们内容不相同。就是相当于一个消息未读和已读不回。

信号产生到处理

当进程收到一个信号时,要看pcb结构中block表对这个信号是否屏蔽,若是屏蔽了就将其保存在pending表中,然后等到接触对这个信号的屏蔽之后,再可能到合适的时候处理这个信号;若是没有屏蔽,不过进程可能在执行它的事情,它会先将收到的信号先储存到pending表中,然后同样是等到合适的时候查找handler方法表执行对应方法!然后当执行方法之后同时会将该信号pending表中对应信号清除。

信号在内核中的表示示意图
在这里插入图片描述
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作handler。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次
或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次因为pending表只有一张,比特位储存只储存一次。而实时信号在递达之前产生多次可
以依次放在一个队列里。

sigset_t信号集

在进程pcb结构中pengding和block表中位图结构的signal这个数不一定是整数,不能当作整数来看因为若是操作系统对位图做了扩展整数就不适用了。操作系统提供的系统接口直接使用整数扩展性太差所以他提供了一种信号集sigset_t,这是一种类型的表示,然后位图就用这种信号集来表示,它是一种类型集合,set比特位集合,为什么不直接使用整数,因为操作系统不相信用户用户不能直接修改三张表操作系统提供系统调用用来访问内核中三种表。
每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有
效”和“无效”的含义是该信号是否处于未决状态。
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。当要获取位图结构时就注定了要在用户和内核之间来回拷贝,而减少拷贝次数使用输出型参数更好。
操作系统在应用层设计sigset_t类型,使用户能够通过系统调用访问内核的三张表结构,这个信号集是系统为用户提供的数据类型。
根据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(constsigset_t *set, int signo);//判断信号集中是否包含某种信号

在这里插入图片描述
在这里插入图片描述

函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有
效信号。
函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系
统支持的所有信号。
注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号
这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

信号集屏蔽sigprocmask

这些接口在用户层面上对某个信号进行屏蔽或者添加信号,然后再通过系统调用将信号集写入内核中。
sigprocmask,调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
在这里插入图片描述
在这里插入图片描述
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信
号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后
根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask

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

block表屏蔽信号函数sigprocmask可以屏蔽信号,但是屏蔽了之后他自己又可以解除屏蔽的信号,解铃还须系铃人
如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递
达。

信号集保存sigpending

pending保存信号函数接口sigpending函数可以保存信号。它读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
在这里插入图片描述

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

using namespace std;

void PrintPending(sigset_t &pending)
{
    for (int signo = 1; signo <= 31; signo++)
    {
        if (sigismember(&pending, signo))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << "\n\n";
}

void handler(int signo)
{
    cout << "catch a signo: " << signo << endl;
}

int main()
{
    // 4. 我可以将所有的信号都进行屏蔽,信号不就不会被处理了吗? 肯定的!9
    //sigset_t现在是一个类型,而在用户上用这个类型定义变量是在栈上定义的变量,栈属于用户空间,此时是属于用户的
    sigset_t bset, oset;
    sigemptyset(&bset);//对这两个比特位集合做初始化
    sigemptyset(&oset);
    for (int i = 1; i <= 31; i++)
    {
        sigaddset(&bset, i); //将31个普通信号添加信号集Bset中
    }
    sigprocmask(SIG_SETMASK, &bset, &oset);//屏蔽了bset中的所有信号

    sigset_t pending;//再通过sigset_t定义一个信号集
    while (true)
    {
        // 2.1 获取信号集,将这个信号保存下来,通过系统调用
        int n = sigpending(&pending);
        if (n < 0)
            continue;
        // 2.2 打印保存下来的信号集
        PrintPending(pending);
        sleep(1);
    }

    // // 0. 对2号信号进行自定义捕捉
    // signal(2, handler);

    // // 1. 先对2号信号进行屏蔽 --- 数据预备
    // sigset_t bset, oset; // 在哪里开辟的空间???用户栈上的,属于用户区
    // sigemptyset(&bset);
    // sigemptyset(&oset);
    // sigaddset(&bset, 2); // 我们已经把2好信号屏蔽了吗?并没有设置进入到你的进程的task_struct
    // // 1.2 调用系统调用,将数据设置进内核
    // sigprocmask(SIG_SETMASK, &bset, &oset); // 我们已经把2好信号屏蔽了吗?ok

    // // 2. 重复打印当前进程的pending 0000000000000000000000000
    // sigset_t pending;
    // int cnt = 0;
    // while (true)
    // {
    //     // 2.1 获取
    //     int n = sigpending(&pending);
    //     if (n < 0)
    //         continue;
    //     // 2.2 打印
    //     PrintPending(pending);

    //     sleep(1);
    //     cnt++;
    //     // 2.3 解除阻塞
    //     if(cnt == 20)
    //     {
    //         cout << "unblock 2 signo" << endl;
    //         sigprocmask(SIG_SETMASK, &oset, nullptr); // 我们已经把2好信号屏蔽了吗?ok
    //     }
    // }
    // // 3 发送2号 0000000000000000000000010

    return 0;
}

对所有信号进行屏蔽,但是9号和19号信号不能屏蔽,所有当我ctrl+c想要终止进程时,进程给我屏蔽了这个信号,并且将这个此你好在pending表中保存,然后这个进程只有通过发送9号信号才能杀掉
在这里插入图片描述

信号保存之后什么时候处理?处理信号的前提是进程收到信号了,通过pending表可以知晓,然后收到信号之后再查看进程是否对这个信号做屏蔽,若是没有屏蔽待到合适时机处理这个信号,什么时候处理信号当从内核态返回用户态的时候处理信号方法!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值