简单的sleep函数以及不是那么简单的sleep函数

前面的博客进程信号中我们简单地介绍了一下关于进程信号以及信号捕捉的一些基本知识。下面我们就用学到的基本知识来实现一个简单的sleep()函数。

首先了解一下sigaction结构体

struct sigaction {
__sighandler_t sa_handler;
unsigned long sa_flags;
void (*sa_restorer)(void);
sigset_t sa_mask; /* mask last for extensibility */
 };
 

sa_handler的原型是一个参数为int,返回类型为void的函数指针。参数即为信号值,所以信号不能传递除信号值之外的任何信息;

sa_sigaction的原型是一个带三个参数,类型分别为int,struct siginfo ,void ,返回类型为void的函数指针。第一个参数为信号值;第二个参数是一个指向struct siginfo结构的指针,此结构中包含信号携带的数据值;第三个参数没有使用。

sa_mask 指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞。

sa_flags 包含了许多标志位,比较重要的一个是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。即使sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误。

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

void sig_alarm(int signo){
    (void)signo;
}

unsigned int mysleep(unsigned int second){
    //创建sigaction结构体New和old
    struct sigaction New,old;
    New.sa_flags = 0;
    New.sa_handler = sig_alarm;
    sigemptyset(&New.sa_mask);
    //注册信号处理函数
    sigaction(SIGALRM, &New, &old);
    //设置闹钟
    alarm(second);   //这里是否有什么不妥之处
    pause();
    unsigned int unslept = alarm(0);//清空闹钟
    sigaction(SIGALRM, &old, NULL);//恢复默认信号的处理动作
    return unslept;
}

int main(){
    while(1){
        mysleep(2);
        printf("Hello, But I still have a BUG.\n");
    }
    return 0;
}

上面就是一个简单的mysleep的实现。不过有几个问题我们可以思考一下:

  1. 信号处理函数sig_alrm函数神马都没做,为什么要注册它作为SIGALRM的处理函数呢?
  2. 为什么子mysleep函数返回前要恢复SIGALRM信号原来的sigaction?

1.答:因为我们设置SIGAlRM信号递达时执行的是用户自定义函数,传入的是用户自定义的函数的函数指针,所以sig_alrm函数必不可少。
2.答:因为我们模拟的是sleep函数,而sleep函数是不会更改SIGALRM的处理动作的,因此,使用完必须还原。

但是,然而,可是,what?我们上面的代码有BUG!!!!

是的在alarm设置完闹钟之后,如果内核调度优先级更高的进程执行,那么有没有可能在调用pause执行之前SIGALRM信号已经递达了呢?这就引出了竞态条件的概念。

竞态条件

如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件。

针对上面的错误,我们提出一种解决方案,是够可以将SIGALRM信号阻塞,不让它提前抵达呢?答案是明显不行的,因为在解除阻塞到执行pause之间仍有可能执行其它优先级更高的进程。

要是解除屏蔽和挂起进程的操作是原子的就好了。还真有这样的函数,那就是sigsuspend()。

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

就像pause一样,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后才返回,返回值为-1, errno设置为EINTR,调用sigsuspend()函数时信号的屏蔽字由sigmask指定,可以通过指定sigmask临时解除对某个信号的阻塞,然后挂起等待,当sigsuspend函数返回时,进程的信号屏蔽字恢复到原来的值,如果原来是对信号时屏蔽的,送sigsuspend返回后仍然屏蔽。

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

void sig_alrm(int signo){
    (void)signo;
}

unsigned int mysleep(unsigned int nsecs){
    struct sigaction newact, oldact;
    sigset_t newmask, oldmask, suspmask;

    newact.sa_handler = sig_alrm;
    newact.sa_flags = 0;
    //清空信号集
    sigemptyset(&newact.sa_mask);
    //注册信号处理函数
    sigaction(SIGALRM, &newact, &oldact);

    //将SIGALRM信号添加到信号屏蔽字中
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGALRM);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);
    //设置闹钟
    alarm(nsecs);
    //旧的信号屏蔽字没有SIGALRM信号
    //将旧的信号屏蔽字赋值给suspmask
    suspmask = oldmask;
    //确保susmask信号屏蔽字没有SIGALRM信号
    sigdelset(&suspmask, SIGALRM);
    //调用sigsuspend函数解除对SIGALRM信号的屏蔽并将进程挂起
    sigsuspend(&suspmask);
    unsigned int unslept = alarm(0);
    //恢复默认信号处理动作
    sigaction(SIGALRM, &oldact, NULL);
    //恢复默认信号屏蔽字
    sigprocmask(SIG_SETMASK, &oldmask, NULL);
    return unslept;
}

int main(){
    while(1){
        mysleep(2);
        printf("Hello, I don't have BUG now.\n");
    }
    return 0;
}

上面就是关于如何模拟实现一个sleep函数以及竞态条件的介绍。由于本人能力有限,上面的介绍可能有不妥或者BUG,欢迎发邮件到我的邮箱(Cyrus_wen@163.com)批评指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值