Hi misc@
If I set a timer via kqueue, the timer is delivered 9–12ms late. This is
one a i7-13700K, but I see this happen consistency on other hardware.
Minimal reproducer:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/event.h>
#include <unistd.h>
int main(void) {
const int timeout_ms = 40;
printf("Running %dms timers.\n", timeout_ms);
int kq = kqueue();
if (kq == -1) {
perror("kqueue");
return 1;
}
struct kevent ev;
EV_SET(&ev, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, timeout_ms, NULL);
for (int i = 0; i < 10; i++) {
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
struct kevent changed;
if (kevent(kq, &ev, 1, &changed, 1, NULL) == -1) {
perror("kevent");
close(kq);
return 1;
}
clock_gettime(CLOCK_MONOTONIC, &end);
long duration_ms = (end.tv_sec - start.tv_sec) * 1000 +
(end.tv_nsec - start.tv_nsec) / 1000000;
printf("duration: %ldms\n", duration_ms);
}
close(kq);
return 0;
}
Sample output of the above:
> cc -o /tmp/minimal_reproducer/timer /tmp/minimal_reproducer/src/main.c &&
/tmp/minimal_reproducer/timer
Running 40ms timers.
duration: 42ms
duration: 49ms
duration: 49ms
duration: 49ms
duration: 50ms
duration: 49ms
duration: 50ms
duration: 49ms
duration: 50ms
duration: 49ms
I see similar results when using gettimeofday instead of clock_gettime.
Using select() instead of kqueue also yields the same results.
The same program on FreeBSD prints "duration: 40ms" 100% of the time. I
haven't tried other BSD variants.
After looking at src, I get the impression that timers are tick-based at
100Hz (based on sys/sys/kernel.h and sys/conf/param.c, but I might be
looking at the wrong thing). That's a granularity of 10ms, and most
likely the explanation of this issue.
If I'm understating everything correctly, the kernel's internal
intrclock interface enabled setting timers with nanosecond precision,
and sets a hardware timer for the next queued timer. Can't userspace
timers be routed to that same plumbing and avoid the 10ms overhead?
--
Hugo