The hardware timer of the PC has another side effect when it comes to dealing with timers.
The "Oversleeping: errors in delays" section 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, ¶m); 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; #else timer.it_value.tv_sec = 0; timer.it_value.tv_nsec = 999847; timer.it_interval.tv_sec = 0; timer.it_interval.tv_nsec = 999847; #endif /* 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) / cpu_freq; /* 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.