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


     阻塞信号:    

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

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

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

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

    how 有三种方式:

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

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

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

    1. int sigpending(sigset_t *set)
    2. // 获取当前 pending 有哪些信号,是输出型参数;
    3. sigprocmask(SIG_SETMASK, &oset, NULL)
    4. // 恢复曾经的信号屏蔽字

     他的底层原理:

    1. int sigaction(int signum, const struct sigaction *act, const struct sigaction *oldact)
    2. // 和sigprocmask一样含义,但是这里的act是个结构体
    3. // 我们重要关注act里的三个成员
    4. act{
    5. sa_flags; // 0
    6. sa_mask; // sigemptyset(&act.sa_mask) 置空
    7. sa_handler // = handler
    8. }

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

    处理信号:

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

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

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

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

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

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

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

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

    可重入函数:

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

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

    volatile 关键字:

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

    1. #include <stdio.h>
    2. #include <signal.h>
    3. #include <unistd.h>
    4. int g_flag = 0;
    5. void handler(int signo)
    6. {
    7. printf("change flag\n");
    8. g_flag = 1;
    9. }
    10. int main()
    11. {
    12. signal(SIGINT, handler);
    13. while (!g_flag){
    14. }
    15. printf("end\n");
    16. return 0;
    17. }

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

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

    1. mytest:test.c
    2. gcc -O3 -o $@ $^
    3. .PHONY:clean
    4. clean:
    5. rm -f mytest

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

  • 相关阅读:
    《canvas》之第12章 其他应用
    Win11、Linux 双系统安装方法
    商城免费搭建之java商城 开源java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c
    基于Python+Flask+MySQL+HTML的B站数据可视化分析系统
    LabVIEW如何才能得到共享变量的引用
    基于HFSS的T型功分波导设计
    Linux之文件查找命令locate与find详解
    22年10月工作笔记整理(前端)
    【java】【项目实战】[外卖九]项目优化(缓存)
    初识AOS --------AOS学习笔记系列
  • 原文地址:https://blog.csdn.net/m0_64645696/article/details/125468996