【Linux】信号(2)如何阻塞、处理信号

本文介绍了信号处理的基本概念,包括信号屏蔽字的设置与恢复,如sigprocmask函数的使用。讨论了信号在何时处理,强调了用户态与内核态的切换。同时提到了可重入函数的概念,以及volatile关键字在多执行流中确保内存可见性的重要性。
摘要由CSDN通过智能技术生成

 阻塞信号:    

        sigset_t 是一个在栈上定义的一个用户级变量,而这些数据添加并不会影响进程,因为 sigset_t 并没有设置进 PCB 内,所以我们必须经过系统调用设置进 OS,才能够影响进程、pending 等。

这里我们需要了解的函数是:

sigprocmask(int how, const sigset_t *set, sigset_t* oset);
// 该函数的作用是读取或更改信号屏蔽字
// 第一个参数是选择哪种方式
// 第二个参数是设置哪个信号
// 第三个参数是一种输出型参数,不需要可以置为NULL

        oset 是可以看作 old set,我们调用函数会对信号屏蔽字进行修改,返回老的信号屏蔽字,万一哪一天想设置回来可以记得。

how 有三种方式:

        1、SIG_BLOCK:添加信号屏蔽字,mask = mask | set;

        2、SIG_UNBLOCK:接触信号阻塞,mask = mask & ~set;

        3、SIG_SETMASK:覆盖信号,mask = set;

int sigpending(sigset_t *set)
// 获取当前 pending 有哪些信号,是输出型参数;

sigprocmask(SIG_SETMASK, &oset, NULL)
// 恢复曾经的信号屏蔽字

 他的底层原理:

int sigaction(int signum, const struct sigaction *act, const struct sigaction *oldact)
// 和sigprocmask一样含义,但是这里的act是个结构体

// 我们重要关注act里的三个成员
act{
    sa_flags; // 0
    sa_mask; // sigemptyset(&act.sa_mask) 置空
    sa_handler // = handler
}

        这里的用法与 sigprocmask 大致相同,所以用 后者即可。

处理信号:

        之前我们说过,信号在接收时不会立即处理,会到“合适”的时候处理,那是什么时候呢?当内核态切换到用户态时,会处理对应的信号。

        什么是用户态:就算执行用户自己的代码,所在的权限是普通权限,叫做用户态;什么是内核态:当发生系统调用时,调用的是内核虚拟地址空间中的代码,必须是内核级别的权限,也就是 OS 通常执行代码的状态的内核态。

        每个进程都有一个用户自定义的用户级页表,在内存中占3个G,还有一个全局的内核页表,在内存中占1个G,也就是我们常看的内存发布图区分的内存。所有的进程都看到的是同一份页表,也就是 OS 的代码和数据,所以进程无论如何切换,都能看到操作系统,但不一定都能访问。

        用户态和内核态相比,内核态权限高,用户态用来执行普通用户的代码,是一种受监管的普通状态。

        什么时候发生切换?当发生系统调用、时间片到了导致进程切换、异常中断、陷阱时就会发生状态切换,反之,当内核态处理完毕时就会返回用户态。

        我们可以看一个概图,分别是自定义和默认情况下,状态的切换顺序:

        问:自定义捕捉时,当前是内核态,可以直接访问用户态上的代码吗?

        答:理论上是可以,但是我们绝不能这样做,因为万一有非法代码放在自定义中,会发生越界问题,比如删了一些本来没有权限删除的数据等等。在 OS 层面上看,OS 不信任任何用户,因为不保证是否是非法代码,但理论上可以这样操作,现实不允许,也就是只能想想。所以得先回到用户态执行,再回到内核态,再回用户态。

可重入函数:

        两种或以上执行流都进入函数当中,叫做重入函数。一个执行流执行函数期间,被中断时处理信号,导致另一个执行流也进入了该函数,叫做该函数可被重入。一个函数被多个执行流进入,如果该函数发生错误,这种现象叫不可重入函数,反之,没事则是可重入函数。

        我们之前调用的函数,STL接口等,是否是可重入的呢?基本全部都是“不可重入函数”,避免内部混乱,malloc / free,I/O 函数都是不可重入的。

volatile 关键字:

        volatile 关键字的含义是保持内存可见性。

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

int g_flag = 0;

void handler(int signo)
{
    printf("change flag\n");
    g_flag = 1;
}

int main()
{
    signal(SIGINT, handler);
    
    while (!g_flag){
    }
    printf("end\n");

    return 0;
}

        当 main 函数中的 while(!flag),flag 是一个全局变量,在多执行流中可能存在对 flag 的修改,但是在编译器优化级别较高时,main 在循环当中,检测到并没有对 flag 进行修改,所以回直接把 flag 放到 CPU 的寄存器中,每次循环都检测寄存器上的 flag,而其他执行流下修改 flag 所在内存上修改的,寄存器上没有修改,所以一直会死循环。

        那需要怎么做到优化呢?需要用到 -O3

mytest:test.c
    gcc -O3 -o $@ $^

.PHONY:clean
clean:
    rm -f mytest

        那该怎么解决优化的问题?可以给变量前面加上 volatile 关键字修饰。编译器会理解成编译时不要把变量优化到寄存器中,不能够直接进寄存器的检测,要从内存读到寄存器中再检测,保持了内存的可见性。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值