​​【信号】信号的处理

信号的处理

信号几时被处理,以前我们说是在合适的时候,这个合适的时候就是当我们的进程内核态返回到用户态时,进行信号的检测和处理内核态就是允许你访问操作系统的代码和数据,用户态是允许访问自己的代码和数据

调用系统调用,操作系统是会自己做身份切换,从用户态变成内核态

int 80,这是一条汇编语句,从用户态陷入内核态

重谈进程地址空间

每个进程都会有自己的地址空间(虚拟地址),虚拟地址提供页表映射到物理内存里。虚拟地址划分两部分,一部分是内核空间,一部分是用户空间,进程属于用户态时就能访问用户空间,属于内核态时可以访问内核空间

用户页表有几份,进程有多少用户页表就有几份,进程具有独立性,那内核级页表有几份,一份,每个进程看到的3-4G的空间(内核空间)是一样的,整个操作系统中,进程再怎么切换,进程看到的内核空间都是一样的

进程视角:我们调用系统的方法,就是在自己的进程地址空间进行的

操作系统视角:任何时刻,都有进程执行,我们要访问操作系统代码时,可以随时访问

操作系统的本质,基于时钟中断的死循环,计算机硬件中,有一个时钟芯片,每个很短的时间,就会向计算机发送时钟中断,所以操作系统可以实时进行进程调度

CPU中有个ecs寄存器,这个寄存器有两个比特位来保存和记录进程的状态的

信号的捕捉

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复
main函数的上下文继续执行了。

一张图让你记住


函数sigaction

 

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体:

将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项,后面写的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数,不详细解释这两个字段

运用sigaction

验证一个问题,一个现象

问题: pending位图,什么时候从1->0.

现象:信号被处理的时候,对应的信号也会被添加到block表中,防止信号捕捉被嵌套调用

验证问题

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


using namespace std;


void PrintPending()
{
    sigset_t set;
    sigpending(&set);
    for (int signo = 1; signo <= 31; signo++)
    {
        if (sigismember(&set, signo))
            cout << "1";
        else
            cout << "0";
    }
    cout << "\n";
}
void handler(int signo)
{
    //验证问题,先打印pending表,观察现象
    PrintPending();
    cout<<"i get a signo: "<<signo<<endl;
}


int main()
{
    struct sigaction bact,oact;
    //初始化结构体为0
    memset(&bact,0,sizeof(bact));
    memset(&oact,0,sizeof(oact));

    //自定义处理函数
    bact.sa_handler=handler;

    //调用函数,捕捉2号信号
    sigaction(2,&bact,&oact);

    while(1)
    {
        cout<<"i am a process,pid: "<<getpid()<<endl;
        sleep(1);
    }

    return 0;
}

从结果发现,二号信号的pending位图置0了,说明执行信号捕捉方法之前,pending表先清0,在调用

验证现象

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


using namespace std;


void PrintPending()
{
    sigset_t set;
    sigpending(&set);
    for (int signo = 1; signo <= 31; signo++)
    {
        if (sigismember(&set, signo))
            cout << "1";
        else
            cout << "0";
    }
    cout << "\n";
}
void handler(int signo)
{
    //验证现象
    cout<<"i get a signo: "<<signo<<endl;
    while(true)
    {
        PrintPending();
        sleep(2);
    }
}

int main()
{
    struct sigaction bact,oact;
    //初始化结构体为0
    memset(&bact,0,sizeof(bact));
    memset(&oact,0,sizeof(oact));

    // 除了屏蔽当前信号,还想添加其他信号的屏蔽--3,4号信号
    sigemptyset(&bact.sa_mask);
    sigaddset(&bact.sa_mask,3);
    sigaddset(&bact.sa_mask,4);

    //自定义处理函数
    bact.sa_handler=handler;

    //调用函数,捕捉2号信号
    sigaction(2,&bact,&oact);

    while(1)
    {
        cout<<"i am a process,pid: "<<getpid()<<endl;
        sleep(2);
    }

    return 0;
}

现象验证成功,信号被处理的时候,对应的信号也会被添加到block表中,防止信号捕捉被嵌套调用 

(1-31信号)非可靠信号在进行注册时,会查看是否已经有相同信号添加到未决集合中,如果有则什么都不做,因此非可靠信号只会添加一次,因此处理完毕后会直接移除(准确来说是先移除,后处理)。而(实时信号)可靠信号会重复添加信号信息到sigqueue链表中,相当于可靠信号可以重复添加,处理完毕后,因为有可能还有相同的信号信息待处理,因此并不会直接移除,而是检测没有相同信号信息后才会从pending集合中移除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱敲代码的奇点

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

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

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

打赏作者

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

抵扣说明:

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

余额充值