今天偶尔发现一个函数,很神奇,当我在考虑其用途时,灵光一现!突然好像明白了sleep底层的实现机制,因此我准备利用此函数来模拟实现一下自己的sleep函数!

核心函数一:pause

1
2
#include<unistd.h>
int pause(void);

当该函数被调用时,进程会自动被挂起,直到有信号递达!

如果收到的信号的动作是结束进程,那么进程将会被结束.

如果该信号处理动作是忽略,那么该进程会继续被挂起.

如果该信号处理动作是捕捉的自定义方法,该函数返回-1.

乍一看,感觉这个函数屁用没有!但如果结合下面这个函数:

核心函数二:alarm

1
2
#include<unistd.h>
int alarm(unsigned int seconds);

该函数可以设置一个闹钟,表示在seconds秒之后操作系统将会给该进程发送一个SIGALRM信号.

该函数返回值为0,或者剩余的秒数.

如果该函数参数为0,则表示取消原有闹钟,并返回原闹钟的剩余时间.

有了这两件"法宝",想实现自己的sleep函数简直是手到擒来,大致思考一下,写出代码如下:

mysleep版本一

1
2
3
4
5
6
7
8
9
10
11
12
#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信号没有恢复到默认操作.

因此,在捕捉信号的"武器",不得不升级一下了!

升级捕捉信号"武器"

1
2
#include<signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);

signo:信号

act:要采取的操作

oact:输出型参数,原有的操作(可为NULL)

1
2
3
4
5
6
struct sigaction{
void (*sa_handler)(int); //信号处理自定义函数
sigset_t sa_mask; //除了将捕捉信号屏蔽之外,还需要额外屏蔽的信号屏蔽字
int sa_flag; //不关心,默认为0
void (*sa_sigaction)(int,siginfo_t*,void*); //处理实时信号的函数
}

这个函数的优点在于,你修改信号捕捉函数后,它有一个输出型参数,把原有的设置备份了一份.因此,当我们程序运行结束时,可以将原有信号处理恢复.

升级mysleep函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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便会该进程一直挂起!!!

要想解决这个问题,我们又得升级武器!

再次升级武器

1
2
#include<signal.h>
int sigsuspend(const sigset_t *sigmask);

这个函数是pause函数的升级版,调用该函数时,进程的信号屏蔽字由参数sigmask指定,可以通过指定sigmask参数来解除对某个信号的屏蔽,然后挂起等待.

为了解决竞态条件的问题,我们需要再次对程序进行升级!

再次升级程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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;
}

完整测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
////////////////////////////////////
//文件说明: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;
}