在Linux环境下模拟实现sleep函数

来源:互联网 发布:淘宝上下架黄金时间 编辑:程序博客网 时间:2024/05/17 18:17

在Linux环境下模拟实现sleep函数

  1 #include <stdio.h>  2 #include<unistd.h>  3 #include<signal.h>  4 void handler(int sig)  5 {}  6   7 int mysleep(int timeout)  8 {  9     struct sigaction act, oact; 10     act.sa_handler = handler; 11     act.sa_flags = 0;  12     sigemptyset(&act.sa_mask);  13       14     alarm(timeout);  15     pause();  16     int ret = alarm(0);  17     sigaction(SIGALRM, &oact, NULL);  18     return ret;  19 }  20   21 int main()  22 {  23     while(1)  24     {  25         printf("I am working up!\n");  26         mysleep(4);  27     } 

或许这个程序正常运行了(至少在我的当时的环境下),但是在系统频繁的切换进程的时候是很容易出现错误的,也就是说,这样的程序,是有Bug的,按以下时序让我们重新审视下这个程序:
1、注册 SIGARM信号的处理函数;
2、调用alarm(nsecs)设定闹钟;
3、内核调度优先级更高的进程取代当前进程执行,且假设有多个,执行的时间超过设定的闹钟;
4、闹钟超时了(但是在此期间并没有响应),内核发送SIGALRM信号给这个进程,处于未决状态。
5、优先级高的继承执行结束,内核要调度回这个进程开始执行。SIGALRM信号递达,执行处理函数之后再次进入内核;
6、返回这个进程的主控制流,alarm(nsecs)返回,调用pause()挂起等待;
7、问题来了,SIGALRM信号已经处理完了,还等待什么?

之所以出现这样的问题根本原因是系统运行的时序不像我们写程序所设想的那样。alarm(nsecs)紧接着的下一行是pause(),但是并不能保证pause()一定会在调用alarm(nsecs)之后的n秒内被调用。异步事件在任何时候都有可能发生(优先级更高的进程),如果写程序的时候考虑不周密,就会出现时序问题导致错误,这个就是竞态条件(Race Condition)。
如何解决上述问题呢,其实也很简单,在调用pause之前屏蔽SIGALRM信号使之不能提前到达就可以了。先看下如下的方法是否可行:
1、屏蔽SIGALRM信号;
2、alarm(nsecs);
3、解除对SIGALRM信号的屏蔽;
4、pause();
但是这还是存在刚才的问题,解除信号屏蔽到调用pause之间有间隙,信号仍可能在此期间到达,那解除屏蔽移除到pause后面呢,显然更不行了,这样pause根本就等不到SIGALRM信号。
这时候或许就能发现问题所在了,只要“解除信号屏蔽”和“挂起等待信号”这两步合并成一个原子操作不就好了,这时候就能用到一个函数了:sigsuspend(包含了pause的挂起等待功能,同时解决了竞态条件的问题),在存在时序问题的场合下都应该调用sigsuspend而不是pause。

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

和pause一样,sigsuspend没有成功返回值,只有一个执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定。可以通过sigmask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字回复为原来的值。如果原来对信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。
以下用sigsuspend重新实现mysleep函数:

 21 int mysleep(int timeout) 22 { 23     struct sigaction act, oact; 24     sigset_t mask,omask,smask; 25     int unslept; 26     act.sa_handler = handler; 27     sigemptyset(&act.sa_mask); 28     act.sa_flags = 0; 29     sigaction(SIGALRM, &act, &oact); 30  31     sigemptyset(&mask); 32     sigaddset(&mask, SIGALRM);                                      33     sigprocmask(SIG_BLOCK, &mask, &omask); 34  35     alarm(timeout); 36  37     smask = omask; 38     sigdelset(&smask, SIGALRM); 39     sigsuspend(&smask); 40  41     unslept = alarm(0); 42     sigaction(SIGALRM, &oact, NULL); 43     sigprocmask(SIG_SETMASK, &omask, NULL); 44     return(unslept); 45 }

如果在调用mysleep函数时没有屏蔽SIGALRM信号:
1、调用sigprocmask(SIG_BLOCK, &mask, &omask);屏蔽SIGALRM
2、调用sigsuspend(&smask);解除对SIGALRM的屏蔽,然后挂起等待
3、SIGALRM递达后suspend返回,自动回复原来的屏蔽字,也就是再次屏蔽SIGALRM
4、调用sigprocmask(SIG_SETMASK, &omask, NULL);再次解除对SIGALRM的屏蔽