Linux高阶——1103—修改屏蔽字&&信号到达及处理流程&&时序竞态问题

目录

1、练习:修改屏蔽字,实现人为阻塞信号

1、定义两个信号集类型set和oset

2、将信号集初始化

3、设置set集合中的signo对应的位置

4、判断某一位的位码是0还是1

5、替换

总代码

2、信号失效的方法

3、高级信号

4、查看未决信号集

总代码

5、信号到达进程及处理流程

可能引起的问题

1、全局变量异步IO

2、可重入和不可重入函数

6、信号引发的时序竞态问题

问题描述

使用定时器模拟竞态问题

解决方法

总代码


1、练习:修改屏蔽字,实现人为阻塞信号

1、定义两个信号集类型set和oset

复制代码
sigset_t set,oset;

2、将信号集初始化

将所有位码初始化为0

复制代码
sigemptyset(&set);

将所有位码初始化为1

复制代码
sigfillset(&set);

3、设置set集合中的signo对应的位置

signo对应的位设置为1

复制代码
sigaddset(&set,signo);

signo对应的位设置为0

复制代码
sigdelset(&set,signo);

4、判断某一位的位码是0还是1

传入信号集地址和信号编号,返回值为位码(0或1)

复制代码
1/0=sigismember(&set,signo);

5、替换

使用set覆盖进程原有的屏蔽字,并传出旧的到oset中

第一个参数------替换方式(SIG_SEMASK为直接替换)

第二个参数------新的信号集

第三个参数------原有的传出的信号集

复制代码
sigprocmask(SIG_SEMASK,&set,&oset);

总代码

复制代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>

int main()
{
    sigset_t set,oset;
    sigemptyset(&set);
    sigaddset(&set,SIGQUIT);
    sigprocmask(SIG_SETMASK,&set,&oset);
    while(1)
        sleep(1);
}

2、信号失效的方法

|--------------|---------------|-----------|
| 行为修改->忽略 | 行为修改->捕捉 | 行为修改->屏蔽 |
| 已递达,但是没有执行动作 | 已递达,但是执行自定义代码 | 未递达,延迟处理 |

3、高级信号

|---------------|----------------|
| SIGKILL(9) | SIGTOP(19) |
| 9号,只要发出必然杀死进程 | 19号,只要发出必然挂起进程 |

系统触发信号相比于用户发送信号,权限更高,用户不能屏蔽系统发出的直接杀死进程的高级信号

如果普通进程杀死高级别的其他用户进程,会因为权限不足失败,比如普通用户不能杀死root用户的进程

4、查看未决信号集

查看进程的信号屏蔽情况,要查看未决信号集

使用sigpend函数,获取进程的未决信号集

复制代码
sigpend(&pset);

总代码

复制代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>

void cat_pset(sigset_t pset)
{
    int i;
    for(i=1;i<32;i++)
    {   
        if((sigismember(&pset,i)))
            putchar('1');
        else
            putchar('0');
    }   
    putchar('\n');
}

int main()
{
    sigset_t set,oset;
    sigemptyset(&set);

    sigaddset(&set,SIGINT);
    sigaddset(&set,SIGQUIT);
    sigaddset(&set,SIGSEGV);
    sigaddset(&set,SIGBUS);
    sigaddset(&set,SIGFPE);
    sigprocmask(SIG_SETMASK,&set,&oset);
    sigset_t pset;
    while(1)
    {   
        sigpending(&pset);
        cat_pset(pset);
        sleep(1);
    }   
}

5、信号到达进程及处理流程

1、程序一般运行在用户层,只有调用函数才会到内核层

2、系统发信号是发给PCB的,PCB在内核层

主函数和捕捉函数都存在于用户层

3、想要处理信号,必须满足两个条件,第一个条件:上切到内核层

上切的情况:系统调用(触发上切)、软件中断(时间片耗尽)、进程异常

因此需要:进程执行系统函数,触发切换

4、完成调用,处置中断,处置异常

系统到内核层,不能直接先处理信号,需要先处理上切的原因,如中断异常等,完成后才能处理信号

5、完成任务后,在返回用户空间前,检测是否有未处理信号,如果有,再处理

6、此信号绑定了捕捉函数,但捕捉函数在用户层

7、再次切换到用户层会浪费资源

因此,内核保持高权限,执行用户层捕捉函数

8、函数执行完毕,返回调用位置

9、main函数从中断的位置继续执行

当切换到内核层时,main就中断了,此时继续执行

一般情况下,带有信号捕捉的进程,永远是main函数先执行,但是执行过程触发信号,永远是捕捉函数先一步执行完毕

可能引起的问题

1、全局变量异步IO

进程中使用信号捕捉技术一定要避免:捕捉函数中使用共享资源,全局变量等......

2、可重入和不可重入函数

insert()函数一般被称为不可重入函数,这些函数中使用了全局或静态资源,会造成隐患

函数中不包含全局变量和静态资源的使用,并且不访问共享资源,只使用临时或局部变量,这种函数是安全的,称为可重入资源

6、信号引发的时序竞态问题

问题描述

使用定时器模拟竞态问题

sleep函数分为两个步骤,定时器alarm和pause挂起进程

alarm定时器定时结束后,会发出SIGALRM信号杀死进程

pause函数只要察觉到任意信号,且那个信号必须有处理动作,pause函数就能立即被唤醒

因此有可能定时没结束,其他信号过来了,pause函数也会被唤醒

只能使用捕捉方式,屏蔽未递达,忽略没有处理动作

设置定时器和接收返回值

复制代码
unsigned int reval;
reval=alarm(seconds);
return reval;

设置信号捕捉

复制代码
void sig_do(int n){}

设置新信号结构体

复制代码
struct sigaction act,oact;
act.sa_handler =sig_do;
act.sa_flags=0;
sigemptyset(&act.sa_mask);

替换行为

复制代码
sigaction(SIGALRM,&act,&oact);

如果在reval=alarm(seconds);后马上调用pause函数,不会有问题,程序正常执行

但是如果加sleep(2),程序不能正常执行

因为alarm定时1秒时,信号已经触发了,此时不会处理信号

sleep是系统函数,会触发系统调用,到内核层,在内核层处理信号后,再去调用pause时,因为信号被处理掉了,因此pause函数不会运行,会被挂起

因此是sleep函数提前处理了SIGALRM函数,导致pause函数因为SIGALRM信号丢失,无法唤醒

解决方法

原子概念:sigsuspend(sigset_t* set);------执行立即挂起,察觉信号唤醒,接触屏蔽字

唤醒时,解除一次屏蔽

设置屏蔽

复制代码
sigset_t set,oset;
sigemptyset(&set);
sigaddset(&set,SIGALRM);
sigprocmask(SIG_SETMASK,&set,&oset);

唤醒的时候同时解除,临时解除,将1变为0,然后还原

复制代码
sigsuspend(&act.sa_mask);

总代码

复制代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>

void sig_do(int n){}

unsigned int mysleep(unsigned int seconds)
{
    unsigned int reval;
    struct sigaction act,oact;
    act.sa_handler =sig_do;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGALRM,&act,&oact);
    sigset_t set,oset;
    sigemptyset(&set);
    sigaddset(&set,SIGALRM);
    sigprocmask(SIG_SETMASK,&set,&oset);
    reval=alarm(seconds);
    sleep(1);
    sigsuspend(&act.sa_mask);
    return reval;
}

int main()
{
    while(1)
    {   
        mysleep(1);
        printf("sleep two seconds\n");
    }   
    return 0;
}
相关推荐
✧北辰٩(ˊωˋ*)و南冥✧17 分钟前
nginx 负载均衡配置(加解决重复登录问题)
运维·nginx·负载均衡
William一直在路上17 分钟前
SpringBoot 拦截器和过滤器的区别
hive·spring boot·后端
慌糖18 分钟前
CentOS 安装 Redis 简明指南
linux·redis·centos
C嘎嘎嵌入式开发25 分钟前
python之set详谈
开发语言·python
定偶26 分钟前
进制转换小题
c语言·开发语言·数据结构·算法
设计师小聂!28 分钟前
linux常用命令(一)
linux·运维·服务器
hnlucky34 分钟前
《Nginx + 双Tomcat实战:域名解析、静态服务与反向代理、负载均衡全指南》
java·linux·服务器·前端·nginx·tomcat·web
hnlucky35 分钟前
同时部署两个不同版本的tomcat要如何配置环境变量
java·服务器·http·tomcat·web
cui_win44 分钟前
【网络】Linux 内核优化实战 - net.ipv4.tcp_congestion_control
linux·网络·tcp/ip
真的想上岸啊1 小时前
学习C++、QT---21(QT中QFile库的QFile读取文件、写入文件的讲解)
c++·qt·学习