linux_时序竞态-pause函数-sigsuspend函数-异步I/O-可重入函数-不可重入函数

接上一篇:linux_信号捕捉-signal函数-sigaction函数-sigaction结构体

  今天来分享时序竞态的知识,关于时序竞态的问题,肯定会和cpu有关,也会学习两个函数,pause函数,sigsuspend函数, 也会分享什么是可重入函数和不可重入函数,话不多说,上一碗时序竞态的大菜:

此博主在CSDN发布的文章目录:【我的CSDN目录,作为博主在CSDN上发布的文章类型导读

在介绍时序竞态之前,先介绍一下pause函数

1.pause函数

函数作用:
  调用该函数可以造成进程主动挂起,等待信号唤醒。调用该系统调用的进程将处于阻塞状态(主动放弃cpu) 直到有信号递达将其唤醒。
头文件:
  #include <unistd.h>
函数原型:
  int pause(void);
函数参数:
  无
返回值:
  返回值:-1 并设置errno为EINTR
    ① 如果信号的默认处理动作是终止进程,则进程终止,pause函数么有机会返回。
    ② 如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回。
    ③ 如果信号的处理动作是捕捉,则【调用完信号处理函数之后,pause返回-1】
    errno设置为EINTR,表示“被信号中断”。想想我们还有哪个函数只有出错返回值。
    ④ pause收到的信号不能被屏蔽,如果被屏蔽,那么pause就不能被唤醒。

1.1.例子–pause函数运用:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void donothing(int signo)
{
}
unsigned int mysleep(unsigned int seconds) 
{
    unsigned int ret;
    struct sigaction act, oldact;
    act.sa_handler = donothing;
    sigemptyset(&act.sa_mask);//信号集清零
    act.sa_flags = 0;
//注册信号捕捉函数
    sigaction(SIGALRM, &act, &oldact);
    alarm(seconds);         //定时固定的秒数  1 
    pause();                //挂起
    ret = alarm(0);  
    sigaction(SIGALRM, &oldact, NULL);  //恢复SIGALRM 默认处理方式
    return ret;
}
int main(void)
{
    mysleep(5);
    return 0;
}

2.时序竞态

  时序竞态: 由于进程之间执行的顺序不同,导致同一个进程多次运行后产生了不同结果的现象。
竞态问题总结:
  竞态条件,跟系统负载有很紧密的关系,体现出信号的不可靠性。系统负载越严重,信号不可靠性越强。
   不可靠由其实现原理所致。信号是通过软件方式实现(跟内核调度高度依赖,延时性强),每次系统调用结束后,或中断处理处理结束后,需通过扫描PCB中的未决信号集,来判断是否应处理某个信号。当系统负载过重时,会出现时序混乱。
   这种意外情况只能在编写程序过程中,提早预见,主动规避,而无法通过gdb程序调试等其他手段弥补。且由于该错误不具规律性,后期捕捉和重现十分困难。

3.时序竞态问题1-信号处理

  为什么会有时序竞态的问题产生,是因为cpu在执行进程的时候,一个进程只执行一个时间片段,所以,你写的程序运行的时候,有时候执行千次万次看似没什么问题,可是某一次突然就崩了,当你去查问题的时候,复查了很长的时间,都没有找到问题,这种问题的出现的概念可能是千万分之一,不容易发生,但一发生就是致命问题,而这种问题还不易发现,只能通过我们日常写代码的经验来避免。
  例如在1.1的例子中,在调用alarm函数后,失去CPU,CPU去执行别的进程了,当执行别的进程的时间大于定时的时间后,会发生什么问题, 如下图。
在这里插入图片描述

  就这样,本能定时1s的程序,成了永久阻塞了,这种情况,是可能发生的,而发生的几率,可能就是千万分之一。
  设想在商业代码中出现这种错误,那后果则是毁灭性的。
  当然,在上述案例中,也有解决方法,那就是利用信号的屏蔽机制来解决,这就得说一下另一个函数sigsuspend了。

3.1.解决时序问题1-sigsuspend函数

函数作用:
  挂起等待信号。
头文件:
  #include <signal.h>
函数原型:
  int sigsuspend(const sigset_t *mask);
函数参数:
  mask:调用该函数期间决定信号屏蔽字得集合
返回值:
  错误返回-1,并设置errno以指示错误(通常为EINTR)。
  EINTR:被一个信号中断。

  可以通过设置屏蔽SIGALRM的方法来控制程序执行逻辑,但无论如何设置,程序都有可能在“解除信号屏蔽”与“挂起等待信号”这个两个操作间隙失去cpu资源。除非将这两步骤合并成一个“原子操作”。sigsuspend函数具备这个功能。在对时序要求严格的场合下都应该使用sigsuspend替换pause。
  原子操作:cpu在执行这个函数就会把他执行完,不会停止

3.2.例子-解决例1.1时序竞态问题1代码:

#include <unistd.h>
#include <signal.h>
#include <stdio.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;

    /*为SIGALRM设置捕捉函数,一个空函数*/
    newact.sa_handler = sig_alrm;
	//将信号集清零
    sigemptyset(&newact.sa_mask);
    newact.sa_flags = 0;
	//注册信号捕捉函数,oldact保留原有的信号集
    sigaction(SIGALRM, &newact, &oldact);

    /*设置阻塞信号集,阻塞SIGALRM信号*/
    sigemptyset(&newmask);//将信号集清零
    sigaddset(&newmask, SIGALRM);//将SIGALRM信号加入信号集,置1
	//屏蔽SIGALRM信号,设置信号屏蔽字,oldmask保留原有的信号集
    sigprocmask(SIG_BLOCK, &newmask, &oldmask); //原子操作,即调用该函数期间不能失去cpu

    //定时nsecs秒,到时后可以产生SIGALRM信号
    alarm(nsecs);

    /*构造一个调用sigsuspend临时有效的阻塞信号集,
     *  在临时阻塞信号集里解除SIGALRM的阻塞*/
    suspmask = oldmask;		//
    sigdelset(&suspmask, SIGALRM);	//在suspmask集合中清除对SIGALRM函数的屏蔽

    /*sigsuspend调用期间,采用临时阻塞信号集suspmask替换原有阻塞信号集
     *  这个信号集中不包含SIGALRM信号,同时挂起等待,
     *  当sigsuspend被信号唤醒返回时,恢复原有的阻塞信号集*/
    sigsuspend(&suspmask); 

    unslept = alarm(0);
    //恢复SIGALRM原有的处理动作,呼应前面注释1
    sigaction(SIGALRM, &oldact, NULL);

    //解除对SIGALRM的阻塞,呼应前面注释2
    sigprocmask(SIG_SETMASK, &oldmask, NULL);
    return(unslept);
}
int main(void)
{
while(1)
{
        mysleep(2);
        printf("Two seconds passed\n");
    }
    return 0;
}

4.时序竞态问题2-全局变量异步I/O

  分析如下父子进程交替数数程序。
  当捕捉函数里面的sleep取消,程序即会出现问题。
  造成该问题出现得原因是什么呢?

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

int n = 0, flag = 0;
void sys_err(char *str)
{
    perror(str);
    exit(1);
}
void do_sig_child(int num)
{
    printf("I am child  %d\t%d\n", getpid(), n);
    n += 2;
    flag = 1;
    sleep(1);
}
void do_sig_parent(int num)
{
    printf("I am parent %d\t%d\n", getpid(), n);
    n += 2;
    flag = 1;
    sleep(1);
}
int main(void)
{
    pid_t pid;
struct sigaction act;

    if ((pid = fork()) < 0)
        sys_err("fork");
    else if (pid > 0) {     
        n = 1;
        sleep(1);
        act.sa_handler = do_sig_parent;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGUSR2, &act, NULL);             //注册自己的信号捕捉函数  父使用SIGUSR2信号
        do_sig_parent(0);						  
        while (1) {
            /* wait for signal */;
           if (flag == 1) {                         //父进程数数完成
                kill(pid, SIGUSR1);
                flag = 0;                        //标志已经给子进程发送完信号
            }
        }
    } else if (pid == 0) {       
        n = 2;
        act.sa_handler = do_sig_child;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGUSR1, &act, NULL);

        while (1) {
            /* waiting for a signal */;
            if (flag == 1) {
                kill(getppid(), SIGUSR2);
                flag = 0;//分析,若是在cpu执行到此处时,收到父进程得信号,在flag还未被改完,就去执行do_sig_child该函数,会怎么样?
            }
        }
    }
    return 0;
}			

  示例中,通过flag变量标记程序实行进度。flag置1表示数数完成。flag置0表示给对方发送信号完成。
   问题出现的位置,在父子进程kill函数之后需要紧接着调用 flag,将其置0,标记信号已经发送。但,在这期间很有可能被kernel调度,失去执行权利,而对方获取了执行时间,通过发送信号回调捕捉函数,从而修改了全局的flag。
  如何解决该问题呢?
  可以使用后续会分享到的“锁”机制。 当操作全局变量的时候,通过加锁、解锁来解决该问题。
现在,我们在编程期间如若使用全局变量,应在主观上注意全局变量的异步IO可能造成的问题。

5.时序竞态问题3-可/不可重入函数

  一个函数在被调用执行期间(尚未调用结束),由于某种时序又被重复调用,称之为“重入”。根据函数实现的方法可分为“可重入函数”和“不可重入函数”两种。

  可重入函数:函数内不能含有全局变量及static变量,不能使用malloc、free等。
  不可重入函数:函数内含有全局变量及static变量,使用malloc、free,是标准I/O函数。

所以,我们的信号捕捉函数应该设计为可重入函数。
信号处理程序可以调用的可重入函数可参阅man 7 signal。

以上就是本次的分享了,希望能对广大网友有所帮助。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
涡量-流函数方法是一种求解流体力学问题的数值方法,适用于二维不可压缩流的稳态和定常问题。下面是使用Python实现求解方腔驱动流问题的代码。 首先,需要导入以下库: ```python import numpy as np import matplotlib.pyplot as plt ``` 接着,定义一些模拟流体的参数: ```python # 模拟参数 L = 1.0 # 正方形腔室的边长 H = 1.0 # 高度 N = 50 # 离散化后网格的数量 dx = L / (N - 1) # 离散化后网格的大小 dt = 0.001 # 时间步长 T = 1000 # 模拟时间 rho = 1.0 # 流体密度 nu = 0.1 # 动力粘度 ``` 然后,初始化流场的速度和压力: ```python # 初始化速度和压力 u = np.zeros((N, N)) v = np.zeros((N, N)) p = np.zeros((N, N)) ``` 接着,定义一个函数来计算涡量和流函数: ```python def get_psi_omega(u, v, dx): # 计算涡量和流函数 psi = np.zeros((N, N)) omega = np.zeros((N, N)) for i in range(1, N - 1): for j in range(1, N - 1): omega[i, j] = ((v[i + 1, j] - v[i - 1, j]) / (2 * dx) - (u[i, j + 1] - u[i, j - 1]) / (2 * dx)) psi[i, j] = psi[i - 1, j] + u[i, j] * dx return psi, omega ``` 接着,定义一个函数来更新速度和压力: ```python def update(u, v, p, rho, nu, dx, dt): # 计算涡量和流函数 psi, omega = get_psi_omega(u, v, dx) # 更新速度 u[1:-1, 1:-1] = (u[1:-1, 1:-1] - dt * (psi[1:-1, 2:] - psi[1:-1, :-2]) / (2 * dx) + dt * nu * ((u[2:, 1:-1] - 2 * u[1:-1, 1:-1] + u[:-2, 1:-1]) / dx**2 + (u[1:-1, 2:] - 2 * u[1:-1, 1:-1] + u[1:-1, :-2]) / dx**2)) v[1:-1, 1:-1] = (v[1:-1, 1:-1] - dt * (psi[2:, 1:-1] - psi[:-2, 1:-1]) / (2 * dx) + dt * nu * ((v[2:, 1:-1] - 2 * v[1:-1, 1:-1] + v[:-2, 1:-1]) / dx**2 + (v[1:-1, 2:] - 2 * v[1:-1, 1:-1] + v[1:-1, :-2]) / dx**2)) # 边界条件 u[0, :] = 0 u[-1, :] = 0 u[:, 0] = 0 u[:, -1] = 1 # 顶部固定为1 v[0, :] = 0 v[-1, :] = 0 v[:, 0] = 0 v[:, -1] = 0 # 更新压力 p_old = np.copy(p) div = ((u[1:-1, 2:] - u[1:-1, :-2]) / (2 * dx) + (v[2:, 1:-1] - v[:-2, 1:-1]) / (2 * dx)) p[1:-1, 1:-1] = ((p[1:-1, 2:] + p[1:-1, :-2]) * dx**2 * rho / (2 * dt) + (p[2:, 1:-1] + p[:-2, 1:-1]) * dx**2 * rho / (2 * dt) - rho * dx**2 * div) / (2 * (dx**2 + dx**2)) # 边界条件 p[0, :] = p[1, :] p[-1, :] = p[-2, :] p[:, 0] = p[:, 1] p[:, -1] = p[:, -2] return u, v, p ``` 接着,进行主循环: ```python # 主循环 for n in range(int(T / dt)): u, v, p = update(u, v, p, rho, nu, dx, dt) # 绘制速度和压力的图像 if n % 100 == 0: plt.clf() plt.quiver(u, v) plt.title(f"流场演化 t = {n * dt:.2f}") plt.pause(0.001) ``` 最后,运行代码并观察结果: ```python plt.ion() plt.figure(figsize=(6, 6)) plt.axis([0, N, 0, N]) plt.gca().set_aspect("equal") for n in range(int(T / dt)): u, v, p = update(u, v, p, rho, nu, dx, dt) # 绘制速度和压力的图像 if n % 100 == 0: plt.clf() plt.quiver(u, v) plt.title(f"流场演化 t = {n * dt:.2f}") plt.pause(0.001) plt.ioff() plt.show() ``` 运行结果如下图所示: ![方腔驱动流问题的模拟结果](https://i.loli.net/2021/05/31/5v7qUo1c6ZPBfSj.png)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

futureCode.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值