Tick, Tock: Understanding the Neutrino Microkernel's Concept of Time

This chapter includes:

What's a tick?

When you're dealing with timing, every moment within the Neutrino microkernel is referred to as a tick. A tick is measured in milliseconds; its initial length is determined by the clock rate of your processor:

Programmatically you can change the clock period via the ClockPeriod() function.

Short delays

If you need a pause, use delay() or the POSIX clock_nanosleep().

If you need a very short delay (e.g., for accessing hardware), you should look at the nanospin*() functions:

They basically do a while loop to a calibrated number of iterations to delay the proper amount of time. This wastes CPU, so you should use it only if necessary.

Oversleeping: errors in delays

The tick size becomes important just about every time you ask the kernel to do something relating to pausing or delaying your process. This includes calls to the following functions:

Normally, you use these functions assuming they'll do exactly what you say: “Sleep for 8 seconds!”, “Sleep for 1 minute!”, and so on. Unfortunately, you get into problems when you say “Sleep for 1 millisecond, ten thousand times!”

Delaying for a second: inaccurate code

Does this code work assuming a 1 ms tick?

void OneSecondPause() {

  /* Wait 1000 milliseconds. */
  for ( i=0; i < 1000; i++ ) delay(1);

Unfortunately, no, this won't return after one second on IBM PC hardware. It'll likely wait for three seconds. In fact, when you call any function based on the nanosleep() or select() functions, with an argument of n milliseconds, it actually takes anywhere from n to infinity milliseconds. But more than likely, this example will take three seconds.

So why exactly does this function take three seconds?

Timer quantization error

What you're seeing is called timer quantization error. One aspect of this error is actually something that's so well understood and accepted that it's even documented in a standard: the POSIX Realtime Extension (1003.1b-1993/1003.1i-1995). This document says that it's all right to delay too much, but it isn't all right to delay too little — the premature firing of a timer is undesirable.

Since the calling of delay() is asynchronous with the running of the clock interrupt, the kernel has to add one clock tick to a relative delay to ensure the correct amount of time (consider what would happen if it didn't, and a one-tick delay was requested just before the clock interrupt went off).

Single sleep

A single 1 ms sleep with error.

That normally adds half a millisecond each time, but in the example given, you end up synchronized with the clock interrupt, so the full millisecond gets tacked on each time.

Twelve sleeps

Twelve 1 ms sleeps with each one's error.

The small error on each sleep accumulates:

Error accumulation

Twelve 1 ms sleeps with the accumulated error.

OK, that should make the loop last 2 seconds — where's the extra second coming from?

The tick and the hardware timer

The problem is that when you request a 1 ms tick rate, the kernel may not be able to actually give it to you because of the frequency of the input clock to the timer hardware. In such cases, it chooses the closest number that's faster than what you requested. In terms of IBM PC hardware, requesting a 1 ms tick rate actually gets you 999,847 nanoseconds between each tick. With the requested delay, that gives us the following:

Since the kernel expires timers only at a clock interrupt, the timer expires after ceil(2.000153) ticks, so each delay(1) call actually waits:

999,847 ns * 3 = 2,999,541 ns

Multiply that by a 1000 for the loop count, and you get a total loop time of 2.999541 seconds.

Delaying for a second: better code

So this code should work?

void OneSecondPause() {

    /* Wait 1000 milliseconds. */
   for ( i=0; i < 100; i++ ) delay(10);

It will certainly get you closer to the time you expect, with an accumulated error of only 1/10 of a second.

Another hiccup with hardware timers

The hardware timer of the PC has another side effect when it comes to dealing with timers. The Oversleeping: errors in delays section above explains the behavior of the sleep-related functions. Timers are similarly affected by the design of the PC hardware.

For example, let's consider the following C code:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/neutrino.h>
#include <sys/netmgr.h>
#include <sys/syspage.h>

int main( int argc, char *argv[] )
    int pid;
    int chid;
    int pulse_id;
    timer_t timer_id;
    struct sigevent event;
    struct itimerspec timer;
    struct _clockperiod clkper;
    struct _pulse pulse;
    uint64_t last_cycles=-1;
    uint64_t current_cycles;
    float cpu_freq;
    time_t start;
    /* Get the CPU frequency in order to do precise time
       calculations. */
    cpu_freq =  SYSPAGE_ENTRY( qtime )->cycles_per_sec;
    /* Set our priority to the maximum, so we won't get disrupted
       by anything other than interrupts. */
        struct sched_param param;
        int ret;

        param.sched_priority = sched_get_priority_max( SCHED_RR );
        ret = sched_setscheduler( 0, SCHED_RR, &param);
        assert ( ret != -1 );
    /* Create a channel to receive timer events on. */
    chid = ChannelCreate( 0 );
    assert ( chid != -1 );
    /* Set up the timer and timer event. */
    event.sigev_notify            = SIGEV_PULSE;
    event.sigev_coid              = ConnectAttach ( ND_LOCAL_NODE,
                                                    0, chid, 0, 0 );
    event.sigev_priority          = getprio(0);
    event.sigev_code              = 1023;
    event.sigev_value.sival_ptr  = (void*)pulse_id;
    assert ( event.sigev_coid != -1 );
    if ( timer_create(  CLOCK_REALTIME, &event, &timer_id ) == -1 )
        perror ( "can't create timer" );
        exit( EXIT_FAILURE );
    /* Change the timer request to alter the behavior. */
    #if 1
          timer.it_value.tv_sec        = 0;
          timer.it_value.tv_nsec       = 1000000;
          timer.it_interval.tv_sec     = 0;
          timer.it_interval.tv_nsec    = 1000000;
          timer.it_value.tv_sec        = 0;
          timer.it_value.tv_nsec       = 999847;
          timer.it_interval.tv_sec     = 0;
          timer.it_interval.tv_nsec    = 999847;
    /* Start the timer. */
    if ( timer_settime( timer_id, 0, &timer, NULL ) == -1 )
        perror("Can't start timer.\n");
        exit( EXIT_FAILURE );
    /* Set the tick to 1 ms. Otherwise if left to the default of
       10 ms, it would take 65 seconds to demonstrate. */
    clkper.nsec       = 1000000;
    clkper.fract      = 0;
    ClockPeriod ( CLOCK_REALTIME, &clkper, NULL, 0  );   // 1ms
    /* Keep track of time. */
    start = time(NULL);
    for( ;; )
        /* Wait for a pulse. */
        pid = MsgReceivePulse ( chid, &pulse, sizeof( pulse ),
                                NULL );
        /* Should put pulse validation here... */
        current_cycles = ClockCycles();
        /* Don't print the first iteration. */
        if ( last_cycles != -1 )
            float elapse = (current_cycles - last_cycles) /
            /* Print a line if the request is 1.05 ms longer than
               requested. */
            if ( elapse > .00105 )
                 printf("A lapse of %f ms occurred at %d seconds\n",
                         elapse, time( NULL ) - start );
        last_cycles = current_cycles;

The program checks to see if the time between two timer events is greater than 1.05 ms. Most people expect that given QNX Neutrino's great realtime behavior, such a condition will never occur, but it will, not because the kernel is misbehaving, but because of the limitation in the PC hardware. It's impossible for the OS to generate a timer event at exactly 1.0 ms; it will be .99847 ms. This has unexpected side effects.

Where's the catch?

As described earlier in this chapter, there's a 153-nanosecond (ns) discrepancy between the request and what the hardware can do. The kernel timer manager is invoked every .999847 ms. Every time a timer fires, the kernel checks to see if the timer is periodic and, if so, adds the number of nanoseconds to the expected timer expiring point, no matter what the current time is. This phenomenon is illustrated in the following diagram:


Actual and expected timer expirations.

The first line illustrates the actual time at which timer management occurs. The second line is the time at which the kernel expects the timer to be fired. Note what happens at 6534: the next value appears not to have incremented by 1 ms, thus the event 6535 won't be fired!

For signal frequencies, this phenomenon is called a beat. When two signals of various frequencies are “added,” a third frequency is generated. You can see this effect if you use your camcorder to record a TV image. Because a TV is updated at 60 Hz, and camcorders usually operate on a different frequency, at playback, you can often see a white line that scrolls in the TV image. The speed of that line is related to the difference in frequency between the camcorder and the TV.

In this case we have two frequencies, one at 1000 Hz, and the other at 1005.495 Hz. Thus, the beat frequency is 1.5 micro Hz, or one blip every 6535 milliseconds.

This behavior has the benefit of giving you the expected number of fired timers, on average. In the example above, after 1 minute, the program would have received 60000 fired timer events (1000 events /sec * 60 sec). If your design requires very precise timing, you have no other choice but to request a timer event of .999847 ms and not 1 ms. This can make the difference between a robot moving very smoothly or scratching your car.

What time is it?

QNX Neutrino maintains two clocks in the system: one is a monotonic count of time since the system booted (CLOCK_MONOTONIC), and the other is a wall-clock time since January 1st, 1970 (CLOCK_REALTIME). The OS actually just counts time since booting, and if asked for the current time, adds an adjustment value (SYSPAGE_ENTRY(qtime)->nsec_tod_adjust) to the monotonic time to get the current time. Any functions that change the current time simply modify the adjustment value.

There are several functions that you can use to determine the current time, for use in timestamps or for calculating execution times, including:

Return the current time in seconds.
Return the current or monotonic time in seconds and nanoseconds since the last second.
Set or get the current or monotonic time in 64-bit nanoseconds.

QNX Neutrino uses unsigned 32-bit values for seconds since January 1st, 1970, allowing for time representations through approximately the year 2100 (rather than 2038). The internal time storage in 64-bit nanoseconds allows for time representations through the year 2500.

All the above methods have a precision based on the system timer tick. If you need more precision, you can use ClockCycles(). This function is implemented differently for each processor, so there are tradeoffs. The implementation tries to be as quick as possible, so it tries to use a CPU register if possible. Because of this, to get accurate times on SMP machines, you need to use thread affinity to lock the thread to a processor, because each processor can have a ClockCycles() base value that may not be synchronized with the values on other processors.

Some caveats for each processor:

Reads 64 bits from one of two CPU registers (the TIMEBASE or a SPR), depending on the processor. A runtime check determines which register to use. On the PPC 400 series, a fault occurs and the fault handler maps the PPC400 SPRs to the normal SPRs.
Reads from a 32-bit register, but this register is privileged. If I/O privileges aren't set, then a fault occurs, and the handler returns the value.
Reads from a 64-bit register, except for 486s, where it causes a fault and the fault handler reads from an external clock chip.
Reads from a 32-bit on-chip clock.
Always faults, and the fault handler reads from an external clock chip to make a 64-bit value.

To convert the cycle number to real time, use SYSPAGE_ENTRY(qtime)->cycles_per_sec.

Note: SH and MIPS are based on a 32-bit clock, so they can wrap around. You should use ClockCycles() only for short durations.

Monitoring execution times

QNX Neutrino includes some special CPU-time clocks that you can use to monitor the execution times for processes and threads.

This mechanism is implemented following the POSIX “Execution Time Monitoring” option, which defines special clock IDs that represent the execution time of a thread or a process:

To obtain: Call: Specifying: Classification
A process CPU-time clock ID clock_getcpuclockid() A process ID, or 0 to get the clock ID for the calling process POSIX
A thread CPU-time clock ID pthread_getcpuclockid() A thread ID in the calling process POSIX
Either of the above ClockId() A process ID and a thread ID QNX Neutrino

A process has permission to get the CPU-time clock ID of any process.

To get the execution time for the process or thread, call clock_gettime() or ClockTime(), passing a process or thread CPU-time clock ID. POSIX also defines the following, which you can use instead of calling clock_getcpuclockid() or pthread_getcpuclockid():

The CPU-time clock ID for the calling process.
The CPU-time clock ID for the calling thread.

QNX Neutrino calculates the execution times for all threads and processes on every clock tick. The full tick interval is added to the running time of the active threads, so the times are approximate.

Here's an example:

int process_clock_id, ret;
struct timespec process_time;

ret = clock_getcpuclockid(0, &process_clock_id);
if (ret != 0) {
    perror ("clock_getcpuid()");
    return (EXIT_FAILURE);

printf ("Process clock ID: %d\n", process_clock_id);
ret = clock_gettime (process_clock_id, &process_time);
if (ret != 0) {
    perror ("clock_gettime()");
    return (EXIT_FAILURE);
printf ("Process clock: %d sec, %ld nsec\n", process_time.tv_sec,

Given that we're getting the execution time for the calling process, we could use CLOCK_PROCESS_CPUTIME_ID instead of calling clock_getcpuclockid():

int process_clock_id, ret;
struct timespec process_time;

ret = clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &process_time);
if (ret != 0) {
    perror ("clock_gettime()");
    return (EXIT_FAILURE);
printf ("Process clock: %d sec, %ld nsec\n", process_time.tv_sec,

Note: You can't use clock_settime() or ClockTime() to set a process or thread CPU-time clock.