Linux下sleep函数原理及实现mysleep
今天偶尔发现一个函数,很神奇,当我在考虑其用途时,灵光一现!突然好像明白了sleep底层的实现机制,因此我准备利用此函数来模拟实现一下自己的sleep函数!
核心函数一:pause
#include<unistd.h>
int pause(void);
当该函数被调用时,进程会自动被挂起,直到有信号递达!
如果收到的信号的动作是结束进程,那么进程将会被结束.
如果该信号处理动作是忽略,那么该进程会继续被挂起.
如果该信号处理动作是捕捉的自定义方法,该函数返回-1.
乍一看,感觉这个函数屁用没有!但如果结合下面这个函数:
核心函数二:alarm
#include<unistd.h>
int alarm(unsigned int seconds);
该函数可以设置一个闹钟,表示在seconds秒之后操作系统将会给该进程发送一个SIGALRM信号.
该函数返回值为0,或者剩余的秒数.
如果该函数参数为0,则表示取消原有闹钟,并返回原闹钟的剩余时间.
有了这两件"法宝",想实现自己的sleep函数简直是手到擒来,大致思考一下,写出代码如下:
mysleep版本一
#include<signal.h>
#include<unistd.h>
void handler(int sig){
//自定义SIGALRM信号处理
}
//(不完善版)函数执行完毕后,出现SIGALRM的信号,都变成自定义操作了
int mysleep1(unsigned int scds){
signal(SIGALRM,handler); //捕捉SIGALRM信号
int ret = alarm(scds); //开始定时
pause(); //进程挂起,直到定时结束
return ret;
}
看着这样的代码,运行起来好像没什么问题,但略微思考一下,就会发现:SIGALRM信号默认操作已经变成捕捉的自定义操作了,函数执行完后,SIGALRM信号没有恢复到默认操作.
因此,在捕捉信号的"武器",不得不升级一下了!
升级捕捉信号"武器"
#include<signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);
signo:信号
act:要采取的操作
oact:输出型参数,原有的操作(可为NULL)
struct sigaction{
void (*sa_handler)(int); //信号处理自定义函数
sigset_t sa_mask; //除了将捕捉信号屏蔽之外,还需要额外屏蔽的信号屏蔽字
int sa_flag; //不关心,默认为0
void (*sa_sigaction)(int,siginfo_t*,void*); //处理实时信号的函数
}
这个函数的优点在于,你修改信号捕捉函数后,它有一个输出型参数,把原有的设置备份了一份.因此,当我们程序运行结束时,可以将原有信号处理恢复.
升级mysleep函数
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void handler(int sig){
}
int mysleep2(unsigned int scds){
//捕捉信号
struct sigaction set,oset;
set.sa_handler = handler;
sigemptyset(&set.sa_mask);
set.sa_flags = 0;
sigaction(SIGALRM,&set,&oset);
//计时
int ret = alarm(scds); //bug
pause();
//恢复信号原有操作
sigaction(SIGALRM,&oset,NULL);
return ret;
}
然而,在这种情况下,该程序还是存在bug.
如果alarm函数执行完毕后,该进程被切出去,cpu去处理了优先级更高的进程,然后再切回该进程时,有可能出现计时已经结束的场景.
那么pause便会该进程一直挂起!!!
要想解决这个问题,我们又得升级武器!
再次升级武器
#include<signal.h>
int sigsuspend(const sigset_t *sigmask);
这个函数是pause函数的升级版,调用该函数时,进程的信号屏蔽字由参数sigmask指定,可以通过指定sigmask参数来解除对某个信号的屏蔽,然后挂起等待.
为了解决竞态条件的问题,我们需要再次对程序进行升级!
再次升级程序
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void handler(int sig){
}
int mysleep3(unsigned int scds){
//捕捉信号
struct sigaction act,oact;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGALRM,&act,&oact);
//屏蔽信号
sigset_t sem,osem,sussem;
sigemptyset(&sem); //初始化
sigaddset(&sem,SIGALRM);
sigprocmask(SIG_BLOCK,&sem,&osem); //设置阻塞信号
alarm(scds);
sussem = osem;
sigdelset(&sussem,SIGALRM); //确保SIGALRM信号不会被阻塞
sigsuspend(&sussem);
int ret = alarm(0);
//恢复信号屏蔽字
sigprocmask(SIG_BLOCK,&osem,NULL);
//恢复捕捉信号
sigaction(SIGALRM,&oact,NULL);
return ret;
}
完整测试代码
////////////////////////////////////
//文件说明:mysleep.c
//作者:高小调
//创建时间:2017年06月26日 星期一 15时02分06秒
//开发环境:Kali Linux/g++ v6.3.0
////////////////////////////////////
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void handler(int sig){
}
int mysleep3(unsigned int scds){
//捕捉信号
struct sigaction act,oact;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGALRM,&act,&oact);
//屏蔽信号
sigset_t sem,osem,sussem;
sigemptyset(&sem); //初始化
sigaddset(&sem,SIGALRM);
sigprocmask(SIG_BLOCK,&sem,&osem); //设置阻塞信号
alarm(scds);
sussem = osem;
sigdelset(&sussem,SIGALRM); //确保SIGALRM信号不会被阻塞
sigsuspend(&sussem);
int ret = alarm(0);
//恢复信号屏蔽字
sigprocmask(SIG_BLOCK,&osem,NULL);
//恢复捕捉信号
sigaction(SIGALRM,&oact,NULL);
return ret;
}
int mysleep2(unsigned int scds){
struct sigaction set,oset;
set.sa_handler = handler;
sigemptyset(&set.sa_mask);
set.sa_flags = 0;
sigaction(SIGALRM,&set,&oset);
int ret = alarm(scds); //bug
pause();
sigaction(SIGALRM,&oset,NULL);
return ret;
}
int mysleep1(unsigned int scds){
signal(SIGALRM,handler);
int ret = alarm(scds);
pause();
return ret;
}
int main(){
while(1){
printf("i came to test mysleep function!\n");
mysleep3(1);
}
return 0;
}