是否有任何表现良好的POSIX间隔定时器?

时间:2012-07-05 06:33:14

标签: c timer posix

受到最后一个闰秒的启发,我一直在使用POSIX调用探索计时(特别是间隔计时器)。

POSIX提供了几种设置计时器的方法,但它们都有问题:

  • sleepnanosleep - 在被信号中断后重新启动会很烦人,并且会引入时钟偏差。你可以通过一些额外的工作避免一些但不是全部的这种偏差,但这些功能使用的是实时时钟,所以这并非没有陷阱。
  • setitimer或更现代的timer_settime - 这些设计为间隔计时器,但它们是每个进程,如果您需要多个活动计时器,这是一个问题。它们也不能同步使用,但这不是什么大问题。
  • clock_gettime一起使用时,
  • clock_nanosleepCLOCK_MONOTONIC似乎是正确答案。 clock_nanosleep支持绝对超时,因此您可以只是睡眠,增加超时,然后重复。在中断之后也很容易重启。不幸的是,这些功能可能也是特定于Linux的:在Mac OS X或FreeBSD上不支持它们。
  • pthread_cond_timedwait可在Mac上使用,可以与gettimeofday一起使用,但是在Mac上它只能与实时时钟配合使用,因此当系统时钟为时,它会受到不当行为的影响设置或闰秒发生。

我缺少一个API吗?是否有一种合理的可移植方式可以在类UNIX系统上创建性能良好的间隔计时器,或者这总结了今天的状态?

表现良好且携带方便,我的意思是:

  • 不容易出现时钟偏差(当然,减去系统时钟自身的偏差)
  • 恢复正在设置的系统时钟或发生闰秒
  • 能够在同一过程中支持多个计时器
  • 至少可在Linux,Mac OS X和FreeBSD上使用

关于闰秒的说明(响应R..'s answer):

POSIX天的长度恰好是86,400秒,但现实世界的日子很少会更长或更短。系统如何解决这种差异是由实现定义的,但闰秒通常与前一秒共享相同的UNIX时间戳。另见:Leap Seconds and What To Do With Them

Linux内核闰第二个错误是在将时钟设置为秒后未能进行内务处理的结果:https://lkml.org/lkml/2012/7/1/203。即使没有那个错误,时钟 也会向后跳一秒钟。

3 个答案:

答案 0 :(得分:5)

POSIX定时器(timer_create)不需要信号;您还可以通过SIGEV_THREAD通知类型安排在线程中传递计时器到期时间。不幸的是,glibc的实现实际上为每个到期创建了一个新线程(它们都有很多开销并且破坏了实时质量鲁棒性的任何希望),尽管标准允许在每个到期时重用相同的线程。

除此之外,我建议您创建自己的线程,使用clock_nanosleep TIMER_ABSTIMECLOCK_MONOTONIC作为间隔计时器。既然你提到一些破碎的系统可能缺少这些接口,你可以简单地在这样的系统上实现一个插入式实现(例如基于pthread_cond_timedwait),并且由于缺乏单调时钟,它可能是低质量的,但这只是使用像MacOSX这样的低质量实现的一个基本限制。

至于你对闰秒的关注,如果ntpd或类似物使你的实时时钟在闰秒发生时向后跳跃,这是ntpd中的一个严重错误。 POSIX时间(自纪元以来的秒数)以标准的日历秒(正好是一天的1/86400)为单位,而不是SI秒,因此唯一的地方闰秒逻辑属于POSIX系统(如果有的话)在mktime / gmtime / localtimetime_t和故障时间之间进行转换时。我没有关注过这个时代的漏洞,但它们似乎是系统软件做了很多愚蠢和错误的事情,而不是任何根本问题。

答案 1 :(得分:5)

kqueuekevent可用于此目的。 OSX 10.6和FreeBSD 8.1增加了对EVFILT_USER的支持,我们可以用它来唤醒另一个线程的事件循环。

请注意,如果您使用它来实现自己的条件和timedwait,则不需要锁定以避免竞争条件,这与this excellent answer相反,因为您不能“错过”队列中的事件。 / p>

来源:

示例代码

使用clang -o test -std=c99 test.c

进行编译
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

// arbitrary number used for the identifier property
const int NOTIFY_IDENT = 1337;

static int kq;

static void diep(const char *s) {
   perror(s);
   exit(EXIT_FAILURE);
}

static void *run_thread(void *arg) {
    struct kevent kev;
    struct kevent out_kev;
    memset(&kev, 0, sizeof(kev));
    kev.ident = NOTIFY_IDENT;
    kev.filter = EVFILT_USER;
    kev.flags = EV_ADD | EV_CLEAR;

    struct timespec timeout;
    timeout.tv_sec = 3;
    timeout.tv_nsec = 0;

    fprintf(stderr, "thread sleep\n");

    if (kevent(kq, &kev, 1, &out_kev, 1, &timeout) == -1)
        diep("kevent: waiting");

    fprintf(stderr, "thread wakeup\n");

    return NULL;
}

int main(int argc, char **argv) {
    // create a new kernel event queue
    kq = kqueue();
    if (kq == -1)
        diep("kqueue()");


    fprintf(stderr, "spawn thread\n");
    pthread_t thread;
    if (pthread_create(&thread, NULL, run_thread, NULL))
        diep("pthread_create");

    if (argc > 1) {
        fprintf(stderr, "sleep for 1 second\n");
        sleep(1);
        fprintf(stderr, "wake up thread\n");

        struct kevent kev;
        struct timespec timeout = { 0, 0 };

        memset(&kev, 0, sizeof(kev));
        kev.ident = NOTIFY_IDENT;
        kev.filter = EVFILT_USER;
        kev.fflags = NOTE_TRIGGER;

        if (kevent(kq, &kev, 1, NULL, 0, &timeout) == -1)
            diep("kevent: triggering");
    } else {
        fprintf(stderr, "not waking up thread, pass --wakeup to wake up thread\n");
    }

    pthread_join(thread, NULL);
    close(kq);
    return EXIT_SUCCESS;
}

输出

$ time ./test
spawn thread
not waking up thread, pass --wakeup to wake up thread
thread sleep
thread wakeup

real    0m3.010s
user    0m0.001s
sys 0m0.002s

$ time ./test --wakeup
spawn thread
sleep for 1 second
thread sleep
wake up thread
thread wakeup

real    0m1.010s
user    0m0.002s
sys 0m0.002s

答案 2 :(得分:1)

您可以查看clock_gettime仿真的here问题,我也提供了答案,但也帮助了我。我最近在一个小型存储库中添加了一个简单的计时器,用于Mac OS X计时,部分模拟POSIX调用。一个简单的测试运行计时器在2000Hz。回购称为PosixMachTiming。尝试一下。

PosixMachTiming基于Mach。似乎一些与时序相关的Mach API已经从Apple的页面中消失了并且已经弃用了,但仍有一些源代码浮动。看起来AbsoluteTime单位和kernel abstractions found here是新的做事方式。无论如何,PosixMachTiming回购仍然适用于我。

PosixMachTiming概述

clock_gettimeCLOCK_REALTIME模拟为一个马赫函数调用,该调用可以触及系统实时时钟,称为CALENDAR_CLOCK

使用全局变量(clock_gettime)为CLOCK_MONOTONIC模拟extern mach_port_t clock_port。当计算机开启或可能唤醒时,此时钟被初始化。我不确定。无论如何,它是函数mach_absolute_time()调用的全局变量。

clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...)使用nanosleep模拟当前时间与绝对单调时间之间的差异。

itimer_start()itimer_step()基于调用clock_nanosleep目标绝对单调时间。它将目标时间增加每次迭代的时间步长(而不是当前时间),这样时钟偏差就不是问题。

请注意,这不能满足您在同一过程中支持多个计时器的要求。