unix系统编程day05--Linux中时序竞态的讲解

本文深入探讨了进程控制中的竞态条件,详细解释了pause函数的作用及其实现的sleep函数,对比了使用pause和sigsuspend函数处理信号的差异。同时,文章分析了时序竞态问题,并提供了sigsuspend函数的解决方案。此外,还讨论了全局变量异步I/O的处理,可重入函数的特点,以及SIGCHLD信号的捕捉与子进程的回收。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

竞态条件(时序竞态)

个人理解:由于系统中各个进程抢占cpu时间,导致本应该按照时间发生的事情没有发生,造成时间上的混乱。

pause函数

  • 函数原型 int pause(void);

  • 作用:挂起当前进程,等待信号来唤醒,处理的信号必须捕捉,否则信号默认动作导致进程直接停止。

  • 返回值:

    1. 如果信号的处理动作为默认动作,则进程终止,pause函数没机会返回
    2. 如果信号的处理动作为忽略操作,则进程处于挂起状态,函数没有机会返回
    3. 如果信号被捕捉,那么首先处理捕捉函数,然后返回-1
    4. pause函数收到的信号不能被屏蔽,否则进程一直挂起,无法被唤醒
  • 使用pause和alarm实现的sleep函数:

#include <iostream>
#include <unistd.h>
#include <signal.h>

using std::cout;
using std::endl;

void fun(int signo) {
    cout << "SIGALRM has catched" << endl;
}

void mysleep(int sec) {
    int ret = alarm(sec);
    if(ret < 0) {
        perror("alarm failed");
        exit(1);
    }
    
    // 标记,下文会用到
    
    ret = pause();
    if(ret == -1 && errno == EINTR) {
        puts("pause success");
    }
}

int main(void) {
    struct sigaction sig;
    sig.sa_handler = fun;
    sigemptyset(&sig.sa_mask);
    sig.sa_flags = 0;

    int ret = sigaction(SIGALRM, &sig, NULL);
    if(ret == -1) {
        perror("sigaction failed");
        exit(1);
    }
    mysleep(3);
    return 0;    
}

时序竞态

前导例

欲睡觉,定闹钟十分钟,十分钟后闹钟叫醒自己
正常:定时,睡觉,十分钟后被叫醒
异常:定时,被叫走劳动二十分钟,然后回来,闹钟已经响过,不能把我唤醒

时序问题分析

我们分析alarm实现的sleep,如果在“标记”处cpu失去时间,当前进程进入等待状态,sec秒过后,然后信号已经发送完毕,但是pause还没有执行,导致进程永久挂起,虽然这种情况比较概率比较低,但是发生一次将会是致命的错误,尤其是在服务器当中

针对解决mysleep的的时序问题

我们考虑,如果在执行pause函数之前我们屏蔽了SIGALRM信号,然后在执行pause函数的同时解除SIGALRM信号的屏蔽,是不是可以让alarm发挥作用,sigsuspend可以做到这一点。

sigsuspend函数
  • 函数原型:int sigsuspend(const sigset_t * mask);
  • 函数作用:主动挂起进程,并且使用mask作为阻塞信号集(信号屏蔽字)
  • 返回值:成功返回-1,并设置errno为EINTR

我们通过sigsuspend实现sleep来避免时序竞态带来的后果,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <iostream>
using std::cout;
using std::endl;

void callback(int signo) {
    puts("signal has catched");
}

void mysleep(unsigned int sec) {
    sigset_t newset, oldset, susmask;
    int ret = 0;
    sigemptyset(&newset);
    sigaddset(&newset, SIGALRM);
    ret = sigprocmask(SIG_BLOCK, &newset, &oldset);
    if(ret == -1) {
        perror("sigprocmask failed");
        exit(1);
    }

    struct sigaction action, oldaction;
    action.sa_handler = callback;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    ret = sigaction(SIGALRM, &action, &oldaction);
    if(ret == -1) {
        perror("sigaction failed");
        exit(1);
    }

    ret = alarm(sec);
    susmask = oldset;
    if(sigismember(&susmask, SIGALRM)) {
        sigdelset(&susmask, SIGALRM);
    }
    sigsuspend(&susmask);
    
    ret = sigaction(SIGALRM, &oldaction, &action);
    if(ret == -1) {
        perror("sigaction failed2");
        exit(1);
    }
    ret = alarm(0);
    sigprocmask(SIG_SETMASK, &oldset, &newset);
}

int main() {
    mysleep(1);
    cout << "***" << endl;
    return 0;
}

全局变量异步I/O

我们使用父进程和子进程轮流数数代码:

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

int n;
pid_t pid;

void parent_fun(int signo) {
    printf("I'm parent, my pid = %d, n = %d\n", getpid(), n);
    n += 2;
    kill(pid, SIGUSR1);
    //sleep(1);
}

void son_fun(int signo) {
    printf("I'm son,    my pid = %d, n = %d\n", getpid(), n);
    n += 2;
    kill(getppid(), SIGUSR2);
    //sleep(1);
}

int main(void) {
    if((pid = fork()) == -1) {
        perror("fork error");
        exit(1);
    } else if(pid > 0) {
        n = 1;
        struct sigaction action;
        action.sa_handler = parent_fun;
        sigemptyset(&action.sa_mask);
        action.sa_flags = 0;
        sigaction(SIGUSR2, &action, NULL);
        sleep(1);
        parent_fun(0);
        while(1) {
        }
    } else {
        n = 2;
        struct sigaction action;
        action.sa_handler = son_fun;
        sigemptyset(&action.sa_mask);
        action.sa_flags = 0;
        sigaction(SIGUSR1, &action, NULL);
        while(1) {
        }
    }
    return 0;
}

可重入/不可重入函数

可重入函数和不可重入的区别,可重入函数不存在全局和static数据结构,malloc/free,也不能使用标准IO。

捕捉SIGCHLD信号

什么时候会产生SIGCHLD信号:
1. 子进程终止死亡。
2. 子进程接收到SIGSTOP信号,进入暂停状态
3. 子进程接收到SIGCONT信号,进入继续执行状态

使用SIGCHLD回收子进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>

void do_wait(int signo) {
    pid_t pid = 0;
    int status;
    //注意下面是while
    while((pid = waitpid(0, &status, WNOHANG)) > 0) {
        if(WIFEXITED(status)) {
            printf("child %d exit with %d\n", pid, WEXITSTATUS(status));
        } else if(WIFSIGNALED(status)) {
            printf("child %d signal by %d\n", pid, WTERMSIG(status));
        }
    }
}

int main(int argc, char ** argv) {
    pid_t pid;
    int i = 0;
    printf("%d %s\n", argc, argv[0]);
    sleep(1);
    for(i = 0; i < 10; i++) {
        if((pid = fork()) == 0) {
            break;
        } else if(pid == -1) {
            perror("fork failed");
            exit(1);
        }
    }
    if(pid == 0) {
        printf("child id = %d\n", getpid());
        sleep(1);
        return i + 1;
    } else if(pid > 0) {
        struct sigaction action;
        action.sa_handler = do_wait;
        action.sa_flags = 0;
        sigemptyset(&action.sa_mask);
        sigaction(SIGCHLD, &action, NULL);
        while(1) {
            printf("parent id = %d\n", getpid());
            sleep(1);
        }
    }
    return 0;
}

中断系统调用

慢速系统调用

介绍:可能会永久阻塞进程的一类,在接收到信号时该函数会中断,不在执行,也可以设置为重新启动,如read,write,pause,wait,waitpid等

其他系统调用

除慢速系统调用的系统调用

守护进程

什么是守护进程

守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。通俗来说就是存在一些进程,一直在运行,不停的监听各种操作,守护进程的的名字后边一般有个d,如ftpd,httpd等。

如何创建守护进程

  1. 创建父进程,并通过父进程fork子进程,父进程退出。
  2. 使用setsid()函数设置子进程为新的会话会长和组长,脱离与父进程的关系
  3. 改变工作目录为根目录
  4. 重新设置umask掩码
  5. 关闭没有必要的文件描述符(把0, 1, 2重定向到/dev/null)
  6. 守护进程主逻辑
  7. 守护进程退出
内容概要:本文详细介绍了扫描单分子定位显微镜(scanSMLM)技术及其在三维超分辨体积成像中的应用。scanSMLM通过电调透镜(ETL)实现快速轴向扫描,结合4f检测系统将不同焦平面的荧光信号聚焦到固定成像面,从而实现快速、大视场的三维超分辨成像。文章不仅涵盖了系统硬件的设计与实现,还提供了详细的软件代码实现,包括ETL控制、3D样本模拟、体积扫描、单分子定位、3D重建和分子聚类分析等功能。此外,文章还比较了循环扫描与常规扫描模式,展示了前者在光漂白效应上的优势,并通过荧光珠校准、肌动蛋白丝、线粒体网络和流感A病毒血凝素(HA)蛋白聚类的三维成像实验,验证了系统的性能和应用潜力。最后,文章深入探讨了HA蛋白聚类与病毒感染的关系,模拟了24小时内HA聚类的动变化,提供了从分子到细胞尺度的多尺度分析能力。 适合人群:具备生物学、物理学或工程学背景,对超分辨显微成像技术感兴趣的科研人员,尤其是从事细胞生物学、病毒学或光学成像研究的科学家和技术人员。 使用场景及目标:①理解和掌握scanSMLM技术的工作原理及其在三维超分辨成像中的应用;②学习如何通过Python代码实现完整的scanSMLM系统,包括硬件控制、图像采集、3D重建和数据分析;③应用于单分子水平研究细胞内结构和动过程,如病毒入侵机制、蛋白质聚类等。 其他说明:本文提供的代码不仅实现了scanSMLM系统的完整工作流程,还涵盖了多种超分辨成像技术的模拟和比较,如STED、GSDIM等。此外,文章还强调了系统在硬件改动小、成像速度快等方面的优势,为研究人员提供了从理论到实践的全面指导。
内容概要:本文详细介绍了基于Seggiani提出的渣层计算模型,针对Prenflo气流床气化炉中炉渣的积累和流动进行了模拟。模型不仅集成了三维代码以提供气化炉内部的温度和浓度分布,还探讨了操作条件变化对炉渣行为的影响。文章通过Python代码实现了模型的核心功能,包括炉渣粘度模型、流动速率计算、厚度更新、与三维模型的集成以及可视化展示。此外,还扩展了模型以考虑炉渣组成对特性的影响,并引入了Bingham流体模型,更精确地描述了含未溶解颗粒的熔渣流动。最后,通过实例展示了氧气-蒸汽流量增加2%时的动响应,分析了温度、流动特性和渣层分布的变化。 适合人群:从事煤气化技术研究的专业人士、化工过程模拟工程师、以及对工业气化炉操作优化感兴趣的科研人员。 使用场景及目标:①评估不同操作条件下气化炉内炉渣的行为变化;②预测并优化气化炉的操作参数(如温度、氧煤比等),以防止炉渣堵塞;③为工业气化炉的设计和操作提供理论支持和技术指导。 其他说明:该模型的实现基于理论公式和经验数据,为确保模型准确性,实际应用中需要根据具体气化炉的数据进行参数校准。模型还考虑了多个物理场的耦合,包括质量、动量和能量守恒方程,能够模拟不同操作条件下的渣层演变。此外,提供了稳求解器和动模拟工具,可用于扰动测试和工业应用案例分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值