Date: Wed, 11 Jul 2012 15:00:47 -0600 From: Ian Lepore <freebsd@damnhippie.dyndns.org> To: Paul Albrecht <albrecht@glccom.com> Cc: freebsd-hackers@freebsd.org Subject: Re: kqueue periodic timer confusion Message-ID: <1342040447.1123.31.camel@revolution.hippie.lan> In-Reply-To: <1342036332.8313.8.camel@albrecht-desktop> References: <1342036332.8313.8.camel@albrecht-desktop>
next in thread | previous in thread | raw e-mail | index | archive | help
On Wed, 2012-07-11 at 14:52 -0500, Paul Albrecht wrote: > Hi, > > Sorry about this repost but I'm confused about the responses I received > in my last post so I'm looking for some clarification. > > Specifically, I though I could use the kqueue timer as essentially a > "drop in" replacement for linuxfd_create/read, but was surprised that > the accuracy of the kqueue timer is much less than what I need for my > application. > > So my confusion at this point is whether this is consider to be a bug or > "feature"? > > Here's some test code if you want to verify the problem: > > #include <stdio.h> > #include <stdlib.h> > #include <string.h> > #include <unistd.h> > #include <errno.h> > #include <sys/types.h> > #include <sys/event.h> > #include <sys/time.h> > > int > main(void) > { > int i,msec; > int kq,nev; > struct kevent inqueue; > struct kevent outqueue; > struct timeval start,end; > > if ((kq = kqueue()) == -1) { > fprintf(stderr, "kqueue error!? errno = %s", strerror(errno)); > exit(EXIT_FAILURE); > } > EV_SET(&inqueue, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 20, 0); > > gettimeofday(&start, 0); > for (i = 0; i < 50; i++) { > if ((nev = kevent(kq, &inqueue, 1, &outqueue, 1, NULL)) == -1) { > fprintf(stderr, "kevent error!? errno = %s", strerror(errno)); > exit(EXIT_FAILURE); > } else if (outqueue.flags & EV_ERROR) { > fprintf(stderr, "EV_ERROR: %s\n", strerror(outqueue.data)); > exit(EXIT_FAILURE); > } > } > gettimeofday(&end, 0); > > msec = ((end.tv_sec - start.tv_sec) * 1000) + (((1000000 + end.tv_usec - start.tv_usec) / 1000) - 1000); > > printf("msec = %d\n", msec); > > close(kq); > return EXIT_SUCCESS; > } > > What you are seeing is "just the way FreeBSD currently works." Sleeping (in most all of its various forms, and I've just looked at the kevent code to verify this is true there) is handled by converting the amount of time to sleep (usually specified in a timeval or timespec struct) to a count of timer ticks, using an internal routine called tvtohz() in kern/kern_time.c. That routine rounds up by one tick to account for the current tick. Whether that's a good idea or not (it probably was once, and probably not anymore) it's how things currently work, and could explain the fairly consistant +1ms you're seeing. Another source of oversleeping is that the length of a tick in microseconds is simplisticly calculated as 1000000 / hz on most hardware, so for HZ=1000, tick=1000. Unless the clock producing the tick interrupts is running at a frequency exactly divisible by 1000, that tick-length calculation has some rounding error in it, and it results in systematic oversleeping. On modern hardware with high-frequency clocks it's typically less than 1%. The routines for sleeping in the kernel take a count of ticks for how long to sleep, so when tvtohz() converts some number of microseconds to the corresponding number of ticks, any rounding error in the value for the length of a tick results in oversleeping by some small percentage of the time you wanted to sleep. Note that this rounding error in calculating the length of a tick does not result in a systematic skew in system timekeeping, because when each tick interrupt happens, the system reads a clock counter register that may or may not be related to the clock producing tick interrupts; the value in the register is full precision without the rounding error you get when counting ticks. It might be an interesting experiment to add kern.hz=10000 to your /boot/loader.conf and see how that affects your test. -- Ian
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?1342040447.1123.31.camel>