C语言 有没有表现良好的POSIX间隔计时器?

x33g5p2x  于 2023-02-07  发布在  其他
关注(0)|答案(5)|浏览(140)

受上一个闰秒的启发,我一直在使用POSIX调用探索计时(特别是间隔计时器)。
POSIX提供了几种设置计时器的方法,但它们都存在问题:

  • sleepnanosleep-这些函数在被信号中断后重新启动会很麻烦,而且它们会引入时钟偏差。您可以通过一些额外的工作来避免部分(但不是全部)偏差,但这些函数使用实时时钟,因此这并非没有陷阱。
  • setitimer或更现代的timer_settime--它们被设计成间隔计时器,但它们是每个进程的,如果你需要多个活动计时器,这是一个问题,它们也不能同步使用,但这不是什么大问题。
  • clock_gettimeclock_nanosleep看起来像是与CLOCK_MONOTONIC一起使用的正确答案。clock_nanosleep支持绝对超时,因此您可以休眠、增加超时并重复。这样也很容易在中断后重新启动。不幸的是,这些函数可能是Linux特有的:Mac OS X和FreeBSD都不支持它们。
  • pthread_cond_timedwait可以在Mac上使用,并可以与gettimeofday一起工作,作为一个笨拙的解决方案,但在Mac上,它只能与实时时钟一起工作,因此当系统时钟设置或闰秒发生时,它会出现错误行为。

是否有什么API我遗漏了?是否有一种合理的可移植的方法来在类UNIX系统上创建行为良好的间隔计时器,或者这是否总结了当今的情况?
我所说的性能良好和合理的便携性,是指:

  • 不易出现时钟偏斜(当然,要减去系统时钟自身的偏斜)
  • 对系统时钟设置或闰秒发生具有弹性
  • 能够在同一进程中支持多个计时器
  • 至少在Linux、Mac OS X和FreeBSD上可用

POSIX日的长度正好是86,400秒,但实际的日很少会更长或更短。系统如何解决这种差异是由实现定义的,但闰秒与前一秒共享相同的UNIX时间戳是常见的。另请参阅:Leap Seconds and What To Do With Them.
Linux内核闰秒错误是由于将时钟设置回一秒后未能进行内务处理而导致的:https://lkml.org/lkml/2012/7/1/203。即使没有这个bug,时钟也会向后跳一秒。

6jygbczu

6jygbczu1#

kqueuekevent可以用于此目的。OSX 10.6和FreeBSD 8.1增加了对EVFILT_USER的支持,我们可以使用它从另一个线程唤醒事件循环。
请注意,如果您使用它来实现自己的condition和timedwait,则与this excellent answer相反,您不需要锁来避免争用条件,因为您不能"错过"队列中的事件。

来源:

示例代码

使用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
e7arh2l6

e7arh2l62#

POSIX定时器(timer_create)不需要信号;您还可以安排通过SIGEV_THREAD通知类型在一个线程中传递计时器到期。不幸的是,glibc的实现实际上为每个到期创建了一个新线程(这既有大量开销,又破坏了实时质量健壮性的希望),尽管标准允许为每个到期重用同一个线程。
除此之外,我建议您创建自己的线程,使用clock_nanosleepTIMER_ABSTIMECLOCK_MONOTONIC作为间隔计时器。(例如基于pthread_cond_timedwait),并且认为其可能由于缺乏单调时钟而质量较低,但这只是使用像MacOSX这样的低质量实现的一个基本限制。
至于您对闰秒的担心,如果ntpd或类似的东西在闰秒发生时使您的实时时钟向后跳,那就是ntpd. POSIX时间中的一个严重错误(自纪元以来的秒数)以日历秒为单位(精确为1/86400天),而不是SI秒,因此闰秒逻辑只属于POSIX系统(如果有的话)是在mktime/gmtime/localtime当他们之间转换time_t和故障时间。I don“我没有跟踪这次出现的bug,但它们似乎是由于系统软件做了很多愚蠢和错误的事情,而不是由于任何根本性的问题。

a9wyjsp7

a9wyjsp73#

你可以看看clock_gettime模拟的问题here,我也提供了答案,但也帮助了我。我最近添加了一个简单的计时器到我为Mac OS X计时保存的小仓库中,它部分模拟POSIX调用。一个简单的测试以2000Hz运行计时器。这个仓库名为PosixMachTiming。试试看。
PosixMachTiming是基于Mach的。似乎一些与定时相关的Mach API已经从苹果的页面中消失,并且已经过时,但是仍然有一些源代码在流传。看起来AbsoluteTime单位和kernel abstractions found here是新的做事方式。无论如何,PosixMachTiming repo仍然对我有效。

PosixMachTiming概述

clock_gettime是通过接入系统实时时钟(称为CALENDAR_CLOCK)的马赫函数调用针对CLOCK_REALTIME仿真的。
clock_gettime是通过使用全局变量(extern mach_port_t clock_port)为CLOCK_MONOTONIC模拟的,这个时钟在电脑开机或者唤醒的时候初始化,我不确定,反正是mach_absolute_time()函数调用的全局变量。
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...)通过使用nanosleep对当前时间和绝对单调时间之间的差进行仿真。
itimer_start()itimer_step()基于调用clock_nanosleep来获取目标绝对单调时间,它在每次迭代时(而不是当前时间)按时间步长递增目标时间,因此时钟偏移不是问题。
请注意,这并不能满足您在同一进程中支持多个计时器的要求。

cbjzeqam

cbjzeqam4#

还有新的CLOCK_TAI,它不需要真实的时钟的闰秒校正。

holgip5t

holgip5t5#

我们可以使用timer_create ()timerfd_create ()。它们的示例在手册页中提供。

相关问题