我们已经在本书中多次使用了函数sleep,我们也在图10.7以及图10.8中展示了两个有缺陷的sleep函数的实现。
#include <unistd.h>
unisgned int sleep(unsigned int seconds);
Returns:0 or number of unslept seconds.
该函数会造成进程挂起直到如下两个条件中至少一个为止:
- seconds指定的系统时间到;
- 一个信号被进程捕获并且该信号处理函数返回;
与函数alarm一样,该函数的实际返回时间可能会比请求的时间稍微晚一些,因为其他系统活动的影响。
在上述第一种情况下,返回值是0,当因为一些信号的出现导致sleep函数提前返回(第二种情况),返回值是还没有sleep完成的秒数。
虽然sleep函数可以使用函数alarm实现(10.10节),但是对此并不做要求.如果同时使用函数alarm与sleep,二者可能会相互影响,而POSIX.1标准对于这些影响并没有相关要求。举例来说,如果我们执行函数sleep(10),等待3秒钟以后,在执行sleep(5),那么会发生什么呢?sleep函数将在5秒钟后返回(假设其他信号并没有在这段时间内被捕获到),但是在2秒钟以后是否会产生信号SIGALRM呢?这一细节与实现有关。
FreeBSD 8.0, Linux 3.2.0, Mac OS X 10.6.8以及Solaris 10使用函数nanosleep函数来实现sleep函数,nanosleep函数可以实现与alarm定时器的独立,为了保证可移植,你不能对sleep函数的实现做任何假设,但是如果你想要混合使用函数sleep与其他任何的时间函数的话,你需要意识到可能的影响。
Example
图10.29显示了POSIX.1中sleep函数的实现,该函数是图10.7中函数实现的一个修改,在图10.7中的可靠地处理了信号,避免了之前实现中可能出现的竞态条件,但是我们仍然没有处理与早先设置的定时器的冲突(虽然这些影响在POSIX.1中并没有定义)
#include "apue.h"
static void sig_alrm(int signo)
{
/*nothing to do,just returning wakes up sigsuspend() */
}
unsigned int sleep(unsigned int seconds)
{
struct sigaction newact, oldact;
sigset_t newmask, oldmask, suspmask;
unsigned int unslept;
/*set our handler, save previous information */
newact.sa_handler = sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM, &newact, &oldact);
/*block SIGALRM and save current signal mask */
sigempty(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
alarm(seconds);
suspmask = oldmask;
/*make sure SIGALRM isn‘t blockedd */
sigdelset(&suspmask, SIGALRM);
/*wait for any signal to be canught */
sigsuspend(&suspmask);
/*some signal has been caught, SIGALRM is now blocked */
unslept = alarm(0);
/*reset previous action */
sigaction(SIGALRM, &oldact, NULL);
/*reset signal mask, which unblocks SIGALRM */
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return (unslept);
}
Figure 10.29 Reliable implementation of sleep
上述实现使用了比图10.7中更多的代码来实现,我们并没有使用分支跳转语句,因此这对于其他的被SIGALRM中断的信号处理函数没有任何影响。
函数nanosleep与sleep函数类似,但是提供了纳秒级的时间精度;
#include <time.h>
int nanosleep(const struct timespec *reqtp, struct timespec *remtp);
Return: 0 if slept for requested time or -1 on error.
该函数将会使得调用进程处于挂起状态,直到请求的时间到达或者是被信号中断。参数reqtp指定了需要睡眠的秒数与纳秒数,如果在睡眠中途被信号中断,且进程没有终止的话,timespec结构指针remtp指向的结构将保存剩余的睡眠时间,如果我们对于未睡眠时间不感兴趣的话,我们可以把这一时间设置为NULL.
如果系统不支持纳秒时间精度的话,请求时间将被向上取整,因为函数nanosleep并不涉及任何信号的产生,我们可以放心大胆地使用它而不用担心与其他函数相互影响。
nanosleep函数曾经属于the Single Unix Specification中的Timers选项,但是在SUSv4版本中被加入到了基本要求。
随着多系统时钟的引入,我们需要一种方法来挂起调用线程,使用一个特殊时钟来实现一段延时。函数clock_nanosleep实现了这一功能。
#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *reqtp, struct timespec *remtp);
Returns: 0 if slept for requested time or error number on failure.
参数clock_id用于指定延时是相对于哪一种时钟时间进行的评估。其可选数值如图6.8所示。参数flags用于控制延时是绝对的还是相对的,当flags被设置为0的时候,睡眠时间是相对的(想要睡眠多久),当其数值被设置为TIMER_ABSTIME的时候,睡眠时间是绝对的,比如说,我们需要一直睡眠直到时钟到达指定的时间,这个时候参数remtp是没有被使用的。其他参数reqtp以及remtp,与函数nanosleep相同。
除了返回错误的情况,调用
clock_nanosleep(CLOCK_REALTIME, 0, reqtp, remtp);
与如下调用有着完全相同的作用
nanosleep(reqtp, remtp);
使用相对睡眠的问题是一些应用对于睡眠的时间要求比较精确,但是相对睡眠时间可能会导致实际睡眠时间比期望的时间更长。举例来说,如果一个应用程序想要周期性执行一个任务,那么它可能需要获取当前时间,然后计算到达下一次执行任务的时间间隔,然后调用函数nanosleep,因为获取到当前时间的时刻与nanosleep函数被调用的时刻可能因为系统调度或者是抢占的原因,导致这两个时间点间隔较长,从而导致实际设置的时间长度比期望的更长,这种情况下,使用一个绝对时间就可以很好地改善定时精度,即是时间共享的调度其可能在睡眠时间已经结束的时候无法保证立即执行我们的任务。
在早期版本的the Single UNIX Specification中,函数clock_nanosleep属于Clock Selection选项,但是在SUSv4中,被移到了基本要求中了。