Are there any well-behaved POSIX interval timers?

前端 未结 4 732
别跟我提以往
别跟我提以往 2020-12-14 08:58

Inspired by the last leap second, I\'ve been exploring timing (specifically, interval timers) using POSIX calls.

POSIX provides several ways to set up timers, but th

相关标签:
4条回答
  • 2020-12-14 09:22

    We can make use of timer_create () or timerfd_create () . Their examples are present in man page .

    0 讨论(0)
  • 2020-12-14 09:24

    You can look at the question here for clock_gettime emulation, which I've also supplied an answer for, but helped me as well. I've recently added a simple timer to a little repository I keep for Mac OS X timing that partially emulates POSIX calls. A simple test runs the timer at 2000Hz. The repo is called PosixMachTiming. Try it out.

    PosixMachTiming is based on Mach. It seems some of the timing-related Mach API has disappeared from Apple's pages and has deprecated, but there are still bits of source code floating around. It looks like AbsoluteTime units and kernel abstractions found here are the new way of doing things. Anyways the PosixMachTiming repo still works for me.

    Overview of PosixMachTiming

    clock_gettime is emulated for CLOCK_REALTIME by a mach function calls that tap into the system realtime clock, dubbed CALENDAR_CLOCK.

    The clock_gettime is emulated for CLOCK_MONOTONIC by using a global variable (extern mach_port_t clock_port). This clock is initialized when the computer turns on or maybe wakes up. I'm not sure. In any case, it's the global variable that the function mach_absolute_time() calls.

    clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...) is emulated by using nanosleep on the difference between current time and the absolute monotonic time.

    itimer_start() and itimer_step() are based on calling clock_nanosleep for a target absolute monotonic time. It increments the target time by the time-step at each iteration (not the current time) so that clock skew is not an issue.

    Note that this does not satisfy your requirement to be able to support multiple timers in the same process.

    0 讨论(0)
  • 2020-12-14 09:39

    kqueue and kevent can be utilized for this purpose. OSX 10.6 and FreeBSD 8.1 add support for EVFILT_USER, which we can use to wake up the event loop from another thread.

    Note that if you use this to implement your own condition and timedwait, you do not need locks in order to avoid race conditions, contrary to this excellent answer, because you cannot "miss" an event on the queue.

    Sources:

    • FreeBSD man page
    • OS X man page
    • kqueue tutorial
    • libevent source code

    Example Code

    Compile with 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;
    }
    

    Output

    $ 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
    
    0 讨论(0)
  • 2020-12-14 09:43

    POSIX timers (timer_create) do not require signals; you can also arrange for the timer expiration to be delivered in a thread via the SIGEV_THREAD notification type. Unfortunately glibc's implementation actually creates a new thread for each expiration (which both has a lot of overhead and destroys any hope of realtime-quality robustness) despite the fact that the standard allows reuse of the same thread for each expiration.

    Short of that, I would just recommend making your own thread that uses clock_nanosleep with TIMER_ABSTIME and CLOCK_MONOTONIC for an interval timer. Since you mentioned that some broken systems might lack these interfaces, you could simply have a drop-in implementation (based e.g. on pthread_cond_timedwait) on such systems, and figure it might be lower-quality due to lack of monotonic clock, but that this is just a fundamental limitation of using a low-quality implementation like MacOSX.

    As for your concern about leap seconds, if ntpd or similar is making your realtime clock jump backwards when a leap second occurs, that's a serious bug in ntpd. POSIX time (seconds since the epoch) are in units of calendar seconds (exactly 1/86400 of a day) per the standard, not SI seconds, and thus the only place leap second logic belongs on a POSIX system (if anywhere) is in mktime/gmtime/localtime when they convert between time_t and broken-down time. I haven't been following the bugs that hit this time, but they seem to have resulted from system software doing a lot of stupid and wrong stuff, not from any fundamental issue.

    0 讨论(0)
提交回复
热议问题