【Linux】模拟实现sleep函数

模拟实现实现sleep函数
首先来了解什么是sleep以及它的工作原理

使用命令 man 3 sleep 查看下系统是怎么介绍sleep函数的
函数原型:当前进程挂起指定时间后继续运行
这里写图片描述
描述:
sleep()函数时用来使进程睡眠直到时间seconds到了或者一个信号(6号信号)到达且没有被忽略。
返回值:
请求的时间到了就返回0,有剩余时间就返回剩余的时间数。
注意: sleep函数有Bug,这个之后再说,先实现sleep
实现原理
1、进程挂起
pause():进程挂起,直到收到一个信号,只有出错返回。
2、计时机制()
alarm():设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发
SIGALRM信号, 该信号的默认处理动作是终⽌止当前进程。这个函数的返回值是0或者是以前设定的闹钟时间还余下 的秒数.

代码实现

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>

void hander(int sig)
{}
int mysleep(int seconds)
{
    struct sigaction act,oldact;
    act.sa_handler = hander;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGALRM,&act,&oldact); //注册信号处理函数

    alarm(seconds);
    pause();

    int _time = alarm(0);
    sigaction(SIGALRM,&oldact,NULL);//恢复默认信号处理动作
    return _time;
}

int main()
{
    while(1)
    {
        mysleep(5);
        printf("I am sleeping....\n");
    }
    return 0;
}

解释上面的代码:
对照下面的图解释
这里写图片描述
1.调用sigaction(SIGALRM,&act,&oldact)函数,注册SIGALRM信号处理函数hander,
2.调用alarm(seconds)设定闹钟。
3. 调用pause等待,内核切换到别的进程运⾏行。
4.seconds秒之后,闹钟超时,内核发SIGALRM给这个进程。
5. 从内核态返回这个进程的⽤用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是hander()。
6. 切换到用户态执行hander函数,进入hander函数时SIGALRM信号被自动屏蔽,从hander函数返回时SIGALRM信号自动解除屏蔽。然后自动执⾏行系统调用sigreturn再次进入 内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。
7. pause函数返回-1,然后调⽤用alarm(0)取消闹钟,调⽤用sigaction恢复SIGALRM信号以前的处理动作.
注意:即使hander函数什么都不做,也必须注册,因为不注册该函数,系统执行默认的函数处理动作。

可能你已经发现上面实现的sleep函数有一个问题:

1进程执行过程中,设定完闹钟后,内核调度优先级更高的进程取代当前的进程,并且这样的进程很多执行时间大于seconds,
2seconds秒后,闹钟响了,内核发送SIGALRM信号给进程,处于未决状态
3优先级更高的进程执行完,内核调度原先的进程执行,SIGALRM信号递达,执行hander函数后再次进入内核
4返回进程的主流控制流程,然后执行pause函数,pause函数挂起等待,但是SIGALRM信号已经处理完了,
这样问题就来了,进程将一直挂起等待。

出现这个问题的根本原因是系统运⾏行的时序(Timing)并不像我们写程序时所设想的那样。虽然alarm(seconds)紧接着的下一⾏行就是pause(),但是无法保证pause()一定会在调⽤用alarm(seconds)之 后的alarm(seconds)秒之内被调用。由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优 先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件 (Race Condition)。

处理该问题:将“解除信号屏蔽”和“挂起等待信号”两步合并成一个原子操作。

sigsuspend函数包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调⽤用sigsuspend⽽而不是pause。

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

代码实现

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>

void hander(int sig)
{}

//解决竞态问题
int mysleep(int seconds)
{
    struct sigaction act,oldact;
    sigset_t mask,omask,susmask;
    act.sa_handler = hander;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGALRM,&act,&oldact);
    sigemptyset(&mask);
    sigaddset(&mask,SIGALRM);
    sigprocmask(SIG_BLOCK,&mask,&omask);

    alarm(seconds);
    susmask = omask;
    sigdelset(&susmask,SIGALRM);
    sigsuspend(&susmask);

    int _time = alarm(0);
    sigaction(SIGALRM,&oldact,NULL);
    sigprocmask(SIG_SETMASK,&omask,NULL);
    return _time;
}

int main()
{
    while(1)
    {
        mysleep(5);
        printf("I am sleeping....\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的屏蔽。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值