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
We can make use of timer_create ()
or timerfd_create ()
.
Their examples are present in man page .
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.
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.
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.
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;
}
$ 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
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.