原理就是先使用定时器定时,然后再使用pause函数或者sigsuspend函数主动阻塞挂起,最终恢复现场。

如果使用pause函数的话,优点是使用简单,缺点是有可能产生时序竞态,导致进程一直阻塞下去:在定时和挂起之间有一个缝隙,有可能定时后因为其他原因没有直接挂起,而是被动挂起或者处理其他信号,但这段时间时钟还在继续计时,当时间到了以后信号就被发送,等回来主动挂起的时候再也等不到那个信号了,因此进程就会被一直挂起。为了解决这个问题,我们在定时前先将SIGALRM信号屏蔽,然后定时、挂起,在挂起的同时我们解除对SIGALRM的屏蔽,这样就不用担心主动挂起前错过信号了,最后恢复现场。

可以根据代码理解一下,其实是一个很符合直觉的过程。需要注意的是pausesigsuspend只有失败返回值-1,不过这个失败的意思是挂起失败,也就是恢复运行,从这个意义上来讲应该是成功返回值,因此我们不要对-1返回值做处理(我顺手处理了,然后一直出错检查了半天)。

代码如下:

Utils.h:里面是一些我封装的函数,为了简化代码

//
// Created by edward on 2021/5/7.
//#ifndef LINUX_UTILS_H
#define LINUX_UTILS_H#include <string>
#include <initializer_list>
#include <signal.h>/*!* 检查系统调用返回值* @param x 返回值* @param msg 错误提示语句* @param y 错误状态,默认为-1*/
void check_error(int x, const std::string &msg = "error", int y = -1);
/*!* 清零mask,并将il中的信号加入到mask中* @param mask* @param il*/
void add2mask(sigset_t *mask, std::initializer_list<int> il);
/*!* 将il中的信号从mask中删除* @param mask* @param il*/
void del2mask(sigset_t *mask, std::initializer_list<int> il);#endif //LINUX_UTILS_H

mysleep函数:
2021.05.11更新:修复了传入参数为0或者负数的bug。如果传入参数都是0的话将导致进程进入阻塞状态无法被唤醒

struct itimerval my_sleep(int seconds, int microseconds) {if (seconds <= 0 && microseconds <= 0)return {0, 0};   //注册SIGALRM信号捕捉函数struct sigaction act, oldact;act.sa_handler = alrm_handler;act.sa_flags = 0;sigset_t mask, oldmask,suspendmask;sigemptyset(&mask);     //屏蔽键盘信号add2mask(&mask, {SIGINT, SIGQUIT, SIGTSTP});act.sa_mask = mask;check_error(sigaction(SIGALRM, &act, &oldact), "sigaction error");//屏蔽alarm信号add2mask(&mask, {SIGALRM});check_error(sigprocmask(SIG_BLOCK, &mask, &oldmask), "sigprocmask error");//设置定时器struct itimerval new_value, old_value;new_value.it_value = {seconds, microseconds};new_value.it_interval = {0, 0};check_error(setitimer(ITIMER_REAL, &new_value, &old_value), "setitimer error");//主动阻塞挂起等待被信号唤醒//pause();      //使用pause会产生竞态,导致信号失效,最终导致进程无限制挂起//通过首先将信号屏蔽防止信号失效,然后再使用sigpending函数在挂起期间解除对ALRM信号的屏蔽,使得进程最终能够被唤醒//在挂起时解除屏蔽alarm信号suspendmask = oldmask;del2mask(&suspendmask, {SIGALRM});sigsuspend(&suspendmask);//恢复现场//恢复SIGALRM信号捕获函数check_error(sigaction(SIGALRM, &oldact, nullptr), "sigaction error");//重置定时器check_error(getitimer(ITIMER_REAL, &new_value));    //获取剩余定时时间old_value.it_interval = {0, 0};old_value.it_value = {0, 0};check_error(setitimer(ITIMER_REAL, &old_value, nullptr), "setitimer error");//解除对ALRM信号的屏蔽add2mask(&mask, {SIGALRM});check_error(sigprocmask(SIG_UNBLOCK, &mask, nullptr), "sigprocmask error");return new_value;   //返回剩余定时时间
}

Utils.cpp:工具类实现,非常简单

//
// Created by edward on 2021/5/7.
//#include "utils.h"using std::string;void check_error(int x, const string &msg, int y) {if (x == y) {perror(msg.c_str());exit(1);}
}void add2mask(sigset_t *mask, std::initializer_list<int> il) {check_error(sigemptyset(mask), "sigemptyset error");for (auto signum : il) {check_error(sigaddset(mask, signum), "sigaddset error");}
}void del2mask(sigset_t *mask, std::initializer_list<int> il) {for (auto signum : il) {check_error(sigdelset(mask, signum), "sigdelset error");}
}

Linux信号实现精确到微秒的sleep函数:通过sigsuspend函数解决时序竞态问题相关推荐

  1. Linux系统编程----8(竞态条件,时序竞态,pause函数,如何解决时序竞态)

    竞态条件(时序竞态): pause 函数 调用该函数可以造成进程主动挂起,等待信号唤醒.调用该系统调用的进程将处于阻塞状态(主动放弃 cpu) 直 到有信号递达将其唤醒,等不到一直等 int paus ...

  2. Linux系统时间和时序,什么是时序竞态 Linux系统时序竞态问题分析

    什么是时序竞态?将同一个程序执行两次,正常情况下,前后两次执行得到的结果应该是一样的.但由于系统资源竞争的原因,前后两次执行的结果有可能得到不一样的结果,这个现象就是时序竞态.时序竞态前导例 在讲时序 ...

  3. Linux信号、进程间关系pause, 时序竞态,sigsuspend

    1. pause函数 --> wait for signal 该函数可以造成进程主动挂起,等待信号唤醒.调用该系统调用的进程将处于阻塞状态(主动放弃CPU)直到有信号递达将其唤醒. int pa ...

  4. linux操作系统之竞态条件(时序竞态)

    (1)时序竞态:前后两次运行同一个程序,出现的结果不同. (2)pause函数:使用该函数会造成进程主动挂起,并等待信号唤醒,调用该系统调用的进程会处于阻塞状态(主动放弃CPU) 函数原型:int p ...

  5. Linux系统编程(五)时序竞态

    时序竞态 产生原因 改进 总结 产生原因 #include <cstdio> #include <stdio.h> #include <sys/time.h> #i ...

  6. linux的基础知识——时序竞态

    文章目录 1.pause函数 1.1 pause函数例子 2.时序问题 3.解决时序问题 4.例子:sigsuspend函数的举例 5.总结 1.pause函数 1.1 pause函数例子 \qqua ...

  7. linux多线程时序问题,Linux时序竞态问题(sleep函数的实现)

    时序竞态是指同样的程序,多次调用运行的结果不同,这是由于争夺系统资源所造成的.比如说我们要使用alarm和pause函数来实现一个sleep的功能,那么由于alarm函数的实现过程并不是一个原子操作, ...

  8. linux 修改微秒时间,Linux系统下精确到微秒级的时间操作函数

    Linux下对时间进行运算,如果是到秒级的,相信大家都用过time之类的函数实现了,但要更精确些呢?到毫秒.微秒级呢?本文引用地址:http://www.eepw.com.cn/article/201 ...

  9. linux -- 信号

    信号及信号来源 使用kill命令杀死进程的实质,是向目标进程发送了一个信号,当目标进程接收到这个信号后,会根据信号的处理函数,执行指 定动作. 比如:keil 杀死进程就是使用的9号信号 使用&quo ...

最新文章

  1. 用友云微服务架构下配置文件管理利器:配置中心
  2. mongodb shell基础命令
  3. Tensorflow的中文网站
  4. ruby nil_Ruby中的数据类型-True,False和Nil用示例解释
  5. 大数据学习(06)-- 云数据库
  6. 数据库名、实例名、数据库域名、全局数据库名、服务名 我也迷糊了
  7. 江苏大学考研计算机录取率,报考数据分析—江苏大学
  8. codeforces round #752
  9. icesat2 重要参数
  10. Python实现C代码统计工具(一)
  11. psd导出jpg太大_保存技巧,完美解决PS导出文件过大的问题
  12. 决战大数据(升级版):大数据的关键思考 - 电子书下载(高清版PDF格式+EPUB格式)...
  13. 一个超简单的Qt数字按键
  14. 数据库建模-物理层建模
  15. 两个乒乓球队进行比赛
  16. Bootstrap简单认识之Tooltips组件
  17. 【qq机器人】发送图片的插件
  18. 使用SketchUp制作球体的方法(图文教程)
  19. 使用Navicat复制MySQL数据库
  20. android系统日志如何查看,Android如何查看系统recovery日志,从而找到系统程序、刷机异常…...

热门文章

  1. DB 数据同步到数据仓库的架构与实践
  2. Gitlab 项目上传
  3. 网页性能优化(初窥)
  4. Android JNI编程(五)——C语言的静态内存分配、动态内存分配、动态创建数组...
  5. Web工程师必备的43款可视化工具
  6. [转载]FPGA/CPLD重要设计思想及工程应用(时序及同步设计)
  7. 缓存应用--Memcached分布式缓存简介(二)
  8. (转)Asp.net 中 Get和Post 的用法
  9. android /data/data/数据作用,android 清除data/data/ 下其他应用的数据
  10. php mysql无限分类排序_PHP 无限级分类、排序