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

news/2024/11/5 11:22:36 标签: linux, 运维, 服务器, c++, 开发语言, 后端

目录

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;
}

http://www.niftyadmin.cn/n/5739408.html

相关文章

练习LabVIEW第三十八题

学习目标&#xff1a; 刚学了LabVIEW&#xff0c;在网上找了些题&#xff0c;练习一下LabVIEW&#xff0c;有不对不好不足的地方欢迎指正&#xff01; 第三十八题&#xff1a; 创建一个VI&#xff0c;实现对按钮状态的指示和按钮“按下”持续时间简单计算功能&#xff0c;按…

C#属性 Property

属性Property不是变量。 它们是由名为访问器方法来实现的一种方法。 实例属性表示的是实例的某个数据&#xff0c;通过这个数据反映实例当前的状态 静态属性表示的是类型的某个数据&#xff0c;通过这个数据反映类型当前的状态 意义&#xff1a; 防止恶意赋值(通过属性间接访问…

微服务day01

MybatisPlus Mp入门 基本步骤 引入MybatisPlus依赖&#xff0c;代替Mybatis依赖 <dependency> <groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </…

命令kill

kill命令用于终止进程 kill [options] <PID> options 选项 kill -9 要强制终止进程 默认情况下&#xff0c;kill 命令发送的是终止信号&#xff08;SIGTERM&#xff0c;信号编号 15&#xff09;&#xff0c;大多数进程在接收到这个信号后会正常结束。 如果进程没有响应…

arm64-v8a 和 armeabi-v7a 有啥区别?

ARM64-v8a 和 ARMEABI-v7a 是 Android 平台上两种不同的 ARM 架构&#xff0c;用于支持应用程序的运行。它们之间有几个关键的区别&#xff1a; 1. 架构类型 ARM64-v8a&#xff1a;代表的是 64 位的 ARM 架构&#xff08;ARMv8-A&#xff09;。它能够处理更大范围的地址空间和…

angular实现list列表和翻页效果

说明&#xff1a;angular实现list列表和翻页效果 上一页 当前页面 下一页 效果图&#xff1a; step1: E:\projectgood\ajnine\untitled4\src\app\car\car.component.css .example-form-fields {display: flex;align-items: flex-start; }mat-list-item{background: antiquew…

MongoDB笔记02-MongoDB基本常用命令

文章目录 一、前言二、数据库操作2.1 选择和创建数据库2.2 数据库的删除 3 集合操作3.1 集合的显式创建3.2 集合的隐式创建3.3 集合的删除 四、文档基本CRUD4.1 文档的插入4.1.1 单个文档插入4.1.2 批量插入 4.2 文档的基本查询4.2.1 查询所有4.2.2 投影查询&#xff08;Projec…

接口测试(十)jmeter——关联(正则表达式提取器)

一、正则表达式 常用的元字符 元字符&#xff1a;用来匹配相关字符 万能匹配表达式&#xff1a; .*? 所有log结尾的文件&#xff1a;*.log 代码说明.匹配除换行符以外的任意字符\w匹配字母或数字或下划线或汉字\s匹配任意的空白符\d匹配数字\b匹配单词的开始或结束^匹配字符…