模拟实现实现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的屏蔽。