Skip site navigation (1)Skip section navigation (2)
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>