今天偶尔发现一个函数,很神奇,当我在考虑其用途时,灵光一现!突然好像明白了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;
}