【操作系统】信号处理与阻塞函数|时序竞态问题
🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️本博客致力于知识分享,与更多的人进行学习交流
关于阻塞函数和信号处理冲突
阻塞函数处于等待状态,等待系统通知或事件消息,如果接收到信号,信号处理函数会中断阻塞函数的执行。这可能导致阻塞函数提前返回。

下面是这一现象的demo程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <signal.h>void sigint(int signo)
{printf("捕捉到了SIGINT %d号信号\n",signo);
}int main()
{struct sigaction act,oact;act.sa_handler=sigint;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGINT,&act,&oact);char buffer[1024];bzero(buffer,sizeof(buffer));int len;
TryAgain:while((len=read(STDIN_FILENO,buffer,sizeof(buffer)))>0){write(STDOUT_FILENO,buffer,sizeof(buffer));bzero(buffer,sizeof(buffer));}if(len==-1){printf("阻塞函数被信号中断,TryAgain。。。\n");goto TryAgain;}return 0;
}

时序竞态
什么是时序竞态?将同一个程序执行两次,正常情况下,前后两次执行得到的结果应该是一样的。但由于系统资源竞争的原因,前后两次执行的结果有可能得到不一样的结果,这个现象就是时序竞态。
pause函数
进程调用pause函数时,会造成进程主动挂起(处于阻塞状态,并主动放弃CPU),并且等待信号将其唤醒。
信号的处理方式有三种:1. 默认;2. 忽略;3. 捕捉。进程收到一个信号后,会先处理响应信号,再唤醒pause函数。于是有下面几种情况:
① 如果信号的默认处理动作是终止进程,则进程将被终止,也就是说一收到信号进程就终止了,pause函数根本就没有机会返回;
② 如果信号的默认处理动作是忽略,则进程将直接忽略该信号,相当于没收到这个信号,进程继续处于挂起状态,pause函数不返回;
③ 如果信号的处理动作是捕捉,则进程调用完信号处理函数之后,pause返回-1,errno设置为EINTR,表示“被信号中断”。
④ pause收到的信号不能被屏蔽,如果被屏蔽,那么pause就不能被唤醒。
因为alarm函数可以在设定的时间之后发送SIGALRM信号,pause函数又可以将进程挂起等待信号,则二者结合可以自己写一个sleep函数
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void sig_alrm(int signo)
{/*空调用,仅负责捕捉,防止信号杀死进程*/
}unsigned int mysleep(unsigned int sec)
{signal(SIGALRM,sig_alrm);unsigned int sleptSec=alarm(sec);//设置一个定时器,定时器结束后发送SIGALRM信号int retNum=pause();//设置进程主动挂起,等待信号将其唤醒。信号的处理行为是捕捉,调用完捕捉函数后返回-1printf("%d\n",retNum);return sleptSec;
}int main()
{while(1){mysleep(2);printf("2s Passed\n");}return 0;
}
pause的返回值是-1

时序竞态问题分析
SIGALRM默认动作是终止进程,因此我们要将其捕捉,对SIGALRM注册信号处理函数;
调用alarm(1)函数定时1秒钟;
alarm(1)调用结束,定时器开始计时。调用完alarm后立即调用了sleep函数,因为调用了系统函数,所以会发生内核层切换,进程失去CPU,进入就绪态等待CPU。使得当前进程无法获得CPU;
alarm函数采用自然定时法,定时器将一直计时,与进程状态无关。于是,1秒后,闹钟定时时间到,内核向当前进程发送SIGALRM信号。
在sleep函数调用完毕后,返回进程前,会检测一下是否有未处理的信号,检测到了SIGALRM信号并进行捕捉处理。
信号处理完毕后,返回当前主控流程,并调用pause()函数,挂起等待alarm函数发送的SIGALRM信号将自己唤醒;
但实际SIGALRM信号已经处理完毕,pause()函数永远不会等到,于是就永远挂起了。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void sig_alrm(int signo)
{/*空调用,仅负责捕捉,防止信号杀死进程*/
}unsigned int mysleep(unsigned int sec)
{signal(SIGALRM,sig_alrm);unsigned int sleptSec=alarm(sec);//设置一个定时器,定时器结束后发送SIGALRM信号sleep(3);//对SIGALRM进行了处理,导致了进程被永远挂起pause();//设置进程主动挂起,等待信号将其唤醒。信号的处理行为是捕捉,调用完捕捉函数后返回-1return sleptSec;
}int main()
{while(1){mysleep(2);printf("2s Passed\n");}return 0;
}
运行结果:由于pause收不到SIGALRM信号,所以被一直挂起了

解决时序竞态问题
在调用sleep函数之前,屏蔽SIGALRM信号,防止sleep对信号进行处理。在sleep调用完成之后,解除对SIGALRM的屏蔽,然后pause()挂起进程等待SIGALRM将其唤醒。
但在解除屏蔽与pause等待挂起信号之间,还是有可能进行其他操作处理SIGALRM,除非将这两个步骤做成一个“原子操作”。Linux系统提供的sigsuspend函数就具备这个功能。所以,在时序要求比较严格的场合下都应该使用sigsuspend函数,而非pause函数。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <signal.h>void sig_alarm(int signo)
{//null 空调用
}unsigned int mysleep(unsigned int seconds)
{//设置捕捉函数struct sigaction act,oldact;act.sa_handler=sig_alarm;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGALRM,&act,&oldact);//设置信号屏蔽sigset_t set,oldset;sigemptyset(&set);sigaddset(&set,SIGALRM);sigprocmask(SIG_SETMASK,&set,&oldset);alarm(2);sleep(3);sigsuspend(&act.sa_mask);
}int main()
{while(1){mysleep(2);printf("two seconds\n");}return 0;
}
Shell脚本实现进程的外部控制
实现一个Shell脚本来对进程进行外部控制,可以使用信号机制来控制进程的启动、停止和继续运行 。Windows常用的任务管理器就是这个机制,利用外部控制的信号来实现进程的挂起、终止、提高优先级等。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <signal.h>void sig_stop(int n)//捕捉SIGUSR1信号
{printf("进程已被杀死\n");exit(0);
}void sig_count(int n)
{printf("进程已被挂起\n");pause();
}void test_sigaction(void)
{struct sigaction act,bct,oact;act.sa_handler=sig_stop;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGUSR1,&act,&oact);bct.sa_handler=sig_count;bct.sa_flags=0;sigemptyset(&bct.sa_mask);sigaction(SIGUSR2,&bct,NULL);
}void output_pid(void)
{int fd=open("config.conf",O_RDWR|O_CREAT,0664);pid_t pid=getpid();char id[10];bzero(id,sizeof(id));sprintf(id,"%d",pid);write(fd,id,strlen(id));close(fd);
}
int main()
{test_sigaction();//设定捕捉output_pid();while(1){printf("this is test...\n");sleep(2);}return 0;
}
#!/bin/bashPID=$(cat config.conf)if [ $1=="stop" ]
then kill -10 $PID #发送SIGUSR1信号
elif [ $1=="cont" ]
then kill -12 $PID #发送SIGUSR2信号
else print "control call faild\n"
fi
运行结果:通过外部脚本成功杀死进程

相关文章:
【操作系统】信号处理与阻塞函数|时序竞态问题
🔥博客主页: 我要成为C领域大神🎥系列专栏:【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于知识分享,与更多的人进行学习交流 关于阻塞函数和…...
go语言day4 引入第三方依赖 整型和字符串转换 进制间转换 指针类型 浮点数类型 字符串类型
Golang依赖下载安装失败解决方法_安装go依赖超时怎么解决-CSDN博客 go安装依赖包(go get, go module)_go 安装依赖-CSDN博客 目录 go语言项目中如何使用第三方依赖:(前两步可以忽略) 一、安装git,安装程序…...
IOS Swift 从入门到精通:闭包第二部分,高级闭包
文章目录 当闭包接受参数时使用闭包作为参数当闭包返回值时使用闭包作为参数简写参数名称高级闭包: 具有多个参数的闭包高级闭包:从函数返回闭包高级闭包:捕获值总结当闭包接受参数时使用闭包作为参数 这是闭包开始变得有点像线路噪声的地方:传递给函数的闭包也可以接受它…...
爬虫超详细介绍
爬虫(Spider)是一种自动化程序,用于在互联网上获取信息。 其工作原理主要可以分为以下几个步骤: 发起请求: 爬虫首先需要向目标网站发起HTTP请求,以获取网页的内容。这个请求可以包含一些额外的信息&…...
双向长短期记忆神经网络BiLSTM
先说一下LSTM LSTM 是一种特殊的 RNN,它通过引入门控机制来解决传统 RNN 的长期依赖问题。 LSTM 的结构包含以下几个关键组件: 输入门(input gate):决定当前时间步的输入信息对细胞状态的影响程度。遗忘门ÿ…...
python基础篇(4):range语句
1 功能介绍 range语句的功能是获得一个数字序列(可迭代类型的一种) 2 语法 语法1: range(num) 获取一个从0开始,到num结束的数字序列(不含num本身) 如range(5)取得的数据是:[0, 1, 2, 3, 4…...
基于STM32的简易计算器proteus仿真设计(仿真+程序+设计报告+讲解视频)
基于STM32的简易计算器proteus仿真设计 讲解视频1.主要功能2. 仿真3. 程序4. 设计报告5. 资料清单&下载链接 基于STM32的简易计算器proteus仿真设计(仿真程序设计报告讲解视频) 仿真图proteus 8.9 程序编译器:keil 5 编程语言:C语言 …...
小程序onLoad 和 onShow
onLoad 和 onShow 是小程序页面的生命周期函数,它们在不同的时机触发,具有不同的用途和执行顺序 1.onLoad: (1)onLoad 在页面加载时触发,仅执行一次。 (2)用于页面的初始化操作,例如…...
抖音直播违规规定有哪些?(直播违禁词汇总表)
全民直播的同时也有不少新手直播玩家处处碰壁,直播间没人气,直播不知道说什么甚至直播间被封。 收到直播封禁通知的朋友,轻者封禁直播账号两三天,严重着可能永久封禁直播间! 今天我们重点来说说直播间被封是怎么回事?如何避免抖音直播间被封?抖音直播间违规规定有哪些?抖音…...
安卓 jetpack compose
以下是 Jetpack Compose 中常用的一些组件的列表: 组件名称描述Text用于显示文本内容。Button可点击的按钮组件,常用于触发事件。TextField用于输入文本的文本框组件。Image用于展示图片。Column垂直布局容器,可以在其中垂直排列子组件。Row…...
JavaWeb系列十九: jQuery的DOM操作 上
查找节点, 修改属性 查找属性节点: 查找到所需要的元素之后, 可以调用jQuery对象的attr()方法用来 设置/返回 它的各种属性值 设置属性值 $(“img”).attr(“width”, “300”);返回属性值 $(“img”).attr(“width”); 创建节点 创建节点: 使用jQuery的工厂函数$(): $(html标…...
JavaWeb系列十一: Web 开发会话技术(Cookie, Session)
韩sir Cookie技术Cookie简单示意图Cookie常用方法Cookie创建Cookie读取JSESSIONID读取指定Cookie Cookie修改Cookie生命周期Cookie的有效路径Cookie作业布置Cookie注意事项Cookie中文乱码问题 Session技术Session原理示意图Session常用方法Session底层机制Session生命周期Sessi…...
【激光雷达使用记录】—— 如何在ubuntu中利用ros自带的rviz工具实时可视化雷达点云的数据
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、查看雷达数据的 frame_id1. 查看雷达数据的话题2. 查看数据的frame_id 二、可视化雷达数据总结 前言 RViz(ROS Visualization)是机…...
一道session文件包含题
目录 环境说明 session文件包含getshell 审计源码 session包含 base64在session中的解码分析 题目: 链接:https://pan.baidu.com/s/1Q0BN08b8gWiVE4tOnirpTA?pwdcate 提取码:cate 环境说明 这里我用的是linux,也可以用p…...
vuex数据持久化
清空原因: 刷新页面vuex的数据会丢失属于正常现象,因为JS的数据都是保存在浏览器的堆栈内存里面的,刷新浏览器页面,以前堆栈申请的内存被释放,这就是浏览器的运行机制,那么堆栈里的数据自然就清空了。 解…...
MySQL之复制(十)
复制 改变主库 确定期望的日志位置 如果有备库和新主库的位置不相同,则需要找到该备库最后一条执行的时间在新主库的二进制日志中相应的位置,然后再执行CHANGE MASTER TO.可以通过mysqlbinlog工具来找到备库执行的最后一条查询,然后在主库上…...
Spring MVC数据绑定和响应——简单数据绑定(一)默认类型数据绑定
一、Spring MVC常见的默认类型 当使用Spring MVC默认支持的数据类型作为处理器的形参类型时,Spring MVC的参数处理适配器会默认识别这些类型并进行赋值。Spring MVC常见的默认类型如下所示。 • HttpServletRequest:获取请求信息。 • HttpServlet…...
短视频平台自动化插件编写需要用到的源代码分享!
随着短视频平台的蓬勃发展,自动化插件的需求也日益增长,这些插件能够帮助用户更高效地管理内容、分析数据、优化发布策略等。 一、登录验证模块 登录验证是自动化插件的基础功能之一,确保用户能够安全地访问平台并执行相关操作,…...
安卓下载以来总是要添加maven下载地址,放在哪?
放这里面的 repositories 里...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...
脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...
