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

竞态条件(时序竞态)

个人理解:由于系统中各个进程抢占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. 守护进程退出
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值