在本书的很多例子中都已使用了sleep函数,在图10-7程序和图10-8程序中有两个sleep的实现,但它们都是有缺陷的。

AMY : 原文错别字 在本书的很多例子中都已使用了sheep函数

#include <unistd.h>
unsigned int sleep(unsigned int seconds);
//返回值:0或未休眠完的秒数

此函数使调用进程被挂起直到满足下面两个条件之一。

  1. 已经过了seconds所指定的墙上时钟时间。
  2. 调用进程捕捉到一个信号并从信号处理程序返回。

如同alarm信号一样,由于其他系统活动,实际返回时间比所要求的会迟一些。

在第1种情形,返回值是0。当由于捕捉到某个信号sleep提早返回时(第2种情形),返回值是未休眠完的秒数(所要求的时间减去实际休眠时间)。

尽管sleep可以用alarm函数(见10.10节)实现,但这并不是必需的。如果使用alarm,则这两个函数之间可能相互影响。POSIX.1 标准对这些相互影响并未做任何说明。例如,若先调用alarm(10),过了3秒后又调用sleep(5),那么将如何呢?sleep将在5秒后返回(假定在这段时间内没有捕捉到另一个信号),但是否在2秒后又产生另一个SIGALRM信号呢?此细节与具体实现有关。

FreeBSD 8.0、Linux 3.2.0、Mac OS X 10.6.8和Solaris10用nanosleep函数实现sleep,使sleep具体实现与信号和闹钟定时器相互独立。考虑到可移植性,不应对sleep的实现进行任何假定,但是如果混合调用sleep和其他与时间有关的函数,则需了解它们之间可能产生的交互。

实例

图10-29给出的是一个POSIX.1 sleep函数的实现。此函数是图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 */
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGALRM);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);

    alarm(seconds);
    suspmask = oldmask;

    /* make sure SIGALRM isn't blocked */
    sigdelset(&suspmask, SIGALRM);

    /* wait for any signal to be caught */
    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);
}

图10-29 sleep的可靠实现

与图10-7相比,为了可靠地实现sleep,图10-29的代码比较长。程序中没有使用任何形式的非局部转移(如图10-8中为了避免在alarmpause之间的竞争条件所做的那样),所以对处理SIGALRM信号期间可能执行的其他信号处理程序没有任何影响。

nanosleep函数与sleep函数类似,但提供了纳秒级的精度。

#include <time.h>
int nanosleep(const struct timespec *reqtp, struct timespec *remtp);
//返回值:若休眠到要求的时间,返回0;若出错,返回−1

这个函数挂起调用进程,直到要求的时间已经超时或者某个信号中断了该函数。reqtp参数用秒和纳秒指定了需要休眠的时间长度。如果某个信号中断了休眠间隔,进程并没有终止,remtp参数指向的 timespec结构就会被设置为未休眠完的时间长度。如果对未休眠完的时间并不感兴趣,可以把该参数置为NULL

如果系统并不支持纳秒这一精度,要求的时间就会取整。因为nanosleep函数并不涉及产生任何信号,所以不需要担心与其他函数的交互。

nanosleep函数过去属于Single UNIX Specification的定时器选项,现已被移至SUSv4的基础部分。

随着多个系统时钟的引入(回忆 6.10 节),需要使用相对于特定时钟的延迟时间来挂起调用线程。clock_nanosleep函数提供了这种功能。

#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flags,
        const struct timespec *reqtp, struct time-spec *remtp);
//返回值:若休眠要求的时间,返回0;若出错,返回错误码

clock_id参数指定了计算延迟时间基于的时钟。时钟标识符列于图6-8中。flags参数用于控制延迟是相对的还是绝对的。flags为0时表示休眠时间是相对的(例如,希望休眠的时间长度),如果flags值设置为TIMER_ABSTIME,表示休眠时间是绝对的(例如,希望休眠到时钟到达某个特定的时间)。

其他的参数reqtpremtp,与nanosleep函数中的相同。但是,使用绝对时间时,remtp参数未使用,因为没有必要。在时钟到达指定的绝对时间值以前,可以为其他的clock_nanosleep调用复用reqtp参数相同的值。

注意,除了出错返回,调用

clock_nanosleep(CLOCK_REALTIME, 0, reqtp, remtp);

和调用

nanosleep(reqtp, remtp);

的效果是相同的。使用相对休眠的问题是有些应用对休眠长度有精度要求,相对休眠时间会导致实际休眠时间比要求的长。例如,某个应用程序希望按固定的时间间隔执行任务,就必须获取当前时间,计算下次执行任务的时间,然后调用nanosleep。在获取当前时间和调用nanosleep之间,处理器调度和抢占可能会导致相对休眠时间超过实际需要的时间间隔。即便分时进程调度程序对休眠时间结束后是否会马上执行用户任务并没有给出保证,使用绝对时间还是改善了精度。

在Single UNIX Specification的早期版本中,clock_nanosleep函数属于时钟选择选项,在SUSv4中,该函数已移至基础部分。

results matching ""

    No results matching ""