当前位置:   article > 正文

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

linux sigpromask

 阻塞信号:    

        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 关键字修饰。编译器会理解成编译时不要把变量优化到寄存器中,不能够直接进寄存器的检测,要从内存读到寄存器中再检测,保持了内存的可见性。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/460177
推荐阅读
相关标签
  

闽ICP备14008679号