linux系统编程之信号(六):竞态条件与sigsuspend函数

2023-10-29 12:08

本文主要是介绍linux系统编程之信号(六):竞态条件与sigsuspend函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、利用pause和alarm函数实现sleep函数

#include <unistd.h>
int pause(void);

pause函数使调用进程挂起直到有信号递达。如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为EINTR,所以pause只有出错的返回值。错误码EINTR表示“被信号中断”。
alarm函数可以参考这里。
下面使用pause和alarm实现sleep(3)函数,称为mysleep:

#include<stdio.h>
#include<signal.h>
#include<unistd.h>void sig_alrm(int signo)
{/* nothing to do */
}unsigned int mysleep(unsigned int nsecs)
{struct sigaction newact, oldact;unsigned int unslept;newact.sa_handler = sig_alrm;sigemptyset(&newact.sa_mask);newact.sa_flags = 0;sigaction(SIGALRM, &newact, &oldact);alarm(nsecs);pause();unslept = alarm(0);sigaction(SIGALRM, &oldact, NULL);return unslept;
}int main(void)
{while (1){mysleep(2);printf("Two seconds passed\n");}return 0;
}
  1. main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig_alrm。
  2. 调用alarm(nsecs)设定闹钟。
  3. 调用pause等待,内核切换到别的进程运行。
  4. nsecs秒之后,闹钟超时,内核发SIGALRM给这个进程。
  5. 从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是sig_alrm。
  6. 切换到用户态执行sig_alrm函数,进入sig_alrm函数时SIGALRM信号被自动屏蔽,从sig_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。
  7. pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作。

需要注意的是虽然sig_alrm函数什么都没干,但还是得注册作为SIGALRM的处理函数,因为SIGALRM信号的默认处理是终止进程,这也是在mysleep函数返回时要恢复SIGALRM信号原来的sigaction的原因。此外,mysleep函数的返回值表示“未睡到”的时间,即unslept,当尚未计时到nsecs而pause函数先被其他信号处理函数所中断返回,在外界看来就是在sleep期间被其他信号处理函数中断了,则mysleep返回非0值,即unslept。如sleep(3)的man 手册写的返回值:
RETURN VALUE: Zero if the requested time has elapsed, or the number of seconds left to sleep, if the call was interrupted by a signal handler.
当然如果是被SIGALRM handler所中断,则表示睡眠时间到,mysleep返回值为0。

二、竞态条件与sigsuspend函数
现在重新审视上面的mysleep函数,设想这样的时序:
1. 注册SIGALRM信号的处理函数。
2. 调用alarm(nsecs)设定闹钟。
3. 内核调度优先级更高的进程取代当前进程执行,并且优先级更高的进程有很多个,每个都要执行很长时间
4. nsecs秒钟之后闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态。
5. 优先级更高的进程执行完了,内核要调度回这个进程执行。SIGALRM信号递达,执行处理函数sig_alrm之后再次进入内核。
6. 返回这个进程的主控制流程,alarm(nsecs)返回,调用pause()挂起等待。
7. 可是SIGALRM信号已经处理完了,还等待什么呢?
出现这个问题的根本原因是系统运行的时序(Timing)并不像我们写程序时所设想的那样。虽然alarm(nsecs)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm(nsecs)之后的nsecs秒之内被调用。由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件(Race Condition)。

如何解决上述问题呢?我们可能会想到,在调用pause之前屏蔽SIGALRM信号使它不能提前递达就可以了。看看以下方法可行吗?
1. 屏蔽SIGALRM信号;
2. alarm(nsecs);
3. 解除对SIGALRM信号的屏蔽;
4. pause();

从解除信号屏蔽到调用pause之间存在间隙,SIGALRM仍有可能在这个间隙递达。要消除这个间隙,我们把解除屏蔽移到pause后面可以吗?
1. 屏蔽SIGALRM信号;
2. alarm(nsecs);
3. pause();
4. 解除对SIGALRM信号的屏蔽;

这样更不行了,还没有解除屏蔽就调用pause,pause根本不可能等到SIGALRM信号。要是“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作就好了,这正是sigsuspend函数的功能。sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend而不是pause。

#include <signal.h>
int sigsuspend(const sigset_t *sigmask);

和pause一样,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。
调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回
时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。
“If a signal is caught and if the signal handler returns, then sigsuspend returns and the signal mask of the process is set to its value before the call to sigsuspend.”

以下用sigsuspend重新实现mysleep函数:

/*************************************************************************
    > File Name: mysleep.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: 2012年12月16日 星期日 21时30分42秒************************************************************************/#include<stdio.h>
#include<signal.h>
#include<unistd.h>void sig_alrm(int signo)
{
    /* nothing to do */
}unsigned int mysleep(unsigned int nsecs)
{
    struct sigaction newact, oldact;
    sigset_t newmask, oldmask, suspmask;
    unsigned int unslept;    /* set our handler, save previous information */
    newact.sa_handler = sig_alrm;
    sigemptyset(&newact.sa_mask);
    newact.sa_flags = 0;
    sigaction(SIGALRM, &newact, &oldact);    /* block SIGALRM and save current signal mask */
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGALRM);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);    alarm(nsecs);    suspmask = oldmask;
    sigdelset(&suspmask, SIGALRM); /* make sure SIGALRM isn't block */    sigsuspend(&suspmask); /* wait for any signal to be caught */    /* some signal has been caught. SIGALRM is now blocked */
    unslept = alarm(0);
    sigaction(SIGALRM, &oldact, NULL); /* reset previous action */    /* reset signal mask, which unblocks SIGALRM */
    sigprocmask(SIG_SETMASK, &oldmask, NULL);
    return(unslept);
}int main(void)
{
    while (1)
    {
        mysleep(2);
        printf("Two seconds passed\n");
    }
    return 0;
}

如果在调用mysleep函数时SIGALRM信号没有屏蔽:
1. 调用sigprocmask(SIG_BLOCK, &newmask, &oldmask);时屏蔽SIGALRM。
2. 调用sigsuspend(&suspmask);时解除对SIGALRM的屏蔽,然后挂起等待待。
3. SIGALRM递达后suspend返回,自动恢复原来的屏蔽字,也就是再次屏蔽SIGALRM。
4. 调用sigprocmask(SIG_SETMASK, &oldmask, NULL);时再次解除对SIGALRM的屏蔽。

程序参考:《linux c 编程一站式学习》
转载自http://blog.csdn.net/jnu_simba/article/details/8949266

这篇关于linux系统编程之信号(六):竞态条件与sigsuspend函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/300534

相关文章

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Kotlin 作用域函数apply、let、run、with、also使用指南

《Kotlin作用域函数apply、let、run、with、also使用指南》在Kotlin开发中,作用域函数(ScopeFunctions)是一组能让代码更简洁、更函数式的高阶函数,本文将... 目录一、引言:为什么需要作用域函数?二、作用域函China编程数详解1. apply:对象配置的 “流式构建器”最

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

SpringIntegration消息路由之Router的条件路由与过滤功能

《SpringIntegration消息路由之Router的条件路由与过滤功能》本文详细介绍了Router的基础概念、条件路由实现、基于消息头的路由、动态路由与路由表、消息过滤与选择性路由以及错误处理... 目录引言一、Router基础概念二、条件路由实现三、基于消息头的路由四、动态路由与路由表五、消息过滤

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决