Tolerant and high-resolution timers

Updated: April 19, 2023

You can use timer tolerance to specify how strictly the kernel should enforce a timer's expiry time.

Tolerance Effect
0 Use the process's default tolerance (see procmgr_timer_tolerance()). If the process's default tolerance is 0, use the default tolerance of 1 tick (see ClockPeriod()).
Greater than 0 and less than one tick (QNX Neutrino 7.0 or later) The timer is considered to be a high-resolution timer; the kernel adjusts the hardware timer so that the clock tick interrupt occurs at the requested tolerance
Greater than or equal to one tick The kernel uses the expiry time plus the tolerance to decide if and for how long it should enter tickless operation or a low-power mode; see Clocks, timers, and power management in this chapter
Amounts beyond any value the current time can have (e.g., ~0ULL) Use infinite tolerance; a CLOCK_SOFTTIME timer is really a timer with infinite tolerance

After creating the timer by calling timer_create() or TimerCreate(), you can set the tolerance by specifying the TIMER_TOLERANCE flag when you call timer_settime() or TimerSettime(). Call one of these functions again (without TIMER_TOLERANCE) to set the expiry time and start the timer. The tolerance persists until you set it again or destroy the timer.

Timer tolerance also applies to the timeouts that you can use for kernel calls that block. You can set the tolerance by specifying the TIMER_TOLERANCE flag when you call timer_timeout() or TimerTimeout(). Call one of these functions again (without TIMER_TOLERANCE) to start the timeout. In this case, the tolerance is used only for the current timeout.

Note:

Here's an example of setting up a high-resolution timer:

struct sigevent event;
timer_t timerId;
int tolerance = 1; // Use a high-resolution timer.
struct itimerspec newTimerTolerance, newTimerSpec;
int rc;

event.sigev_notify = SIGEV_SIGNAL;
event.sigev_signo = SIGUSR1;

rc = timer_create(CLOCK_MONOTONIC, &event, &timerId);
if (rc == -1)
{
  // Handle the error
}

// Set the tolerance on the timer first because
// setting the time activates the timer.

memset(&newTimerTolerance, 0, sizeof(newTimerTolerance));
newTimerTolerance.it_value.tv_sec = 0;
newTimerTolerance.it_value.tv_nsec = tolerance;
newTimerTolerance.it_interval.tv_sec  = 0;
newTimerTolerance.it_interval.tv_nsec = 0;
rc = timer_settime(timerId, TIMER_TOLERANCE, &newTimerTolerance, NULL);
if (rc == -1)
{
  // Handle the error
}

memset(&newTimerSpec, 0, sizeof(newTimerSpec));
newTimerSpec.it_value.tv_sec =  sec;
newTimerSpec.it_value.tv_nsec = usec * 1000;
newTimerSpec.it_interval.tv_sec  = 0;
newTimerSpec.it_interval.tv_nsec = 0;
rc = timer_settime(timerId, 0, &newTimerSpec, NULL);
if (rc == -1)
{
  // Handle the error
}

You can use procmgr_timer_tolerance() to set a default timer tolerance for a process, to be used for timers that don't have a specific tolerance. The default timer tolerance is inherited across a fork(), but not an exec*() or a spawn*(). You can prevent the process's default tolerance from being used for a timer by specifying the TIMER_PRECISE flag when you call timer_settime() or TimerSettime() to set the timer's expiry time. For example:

TIMER_PRECISE Process tolerance Timer tolerance Effective tolerance
Not set Default (0) Default (0) 1 tick
Not set Default (0) 100 ms 100 ms
Not set 200 ms Default (0) 200 ms
Not set 200 ms 100 ms 100 ms
Set Default (0) Default (0) 1 tick
Set Default (0) 100 ms 100 ms
Set 200 ms Default (0) 1 tick
Set 200 ms 100 ms 100 ms

To determine the tolerance for a timer, call TimerInfo(), specifying the _NTO_TI_REPORT_TOLERANCE flag; the function puts the tolerance in the otime.interval_nsec field. For example:

struct _timer_info tinfo;
int tolerance, p_tolerance;

memset(&tinfo, 0, sizeof(struct _timer_info));
rc = TimerInfo ( getpid(), timerId, _NTO_TI_REPORT_TOLERANCE, &tinfo);
if (rc == -1) {
   // Handle the error
}

// Get information about the timer.
if (tinfo.flags &_NTO_TI_PROCESS_TOLERANT) {
   rc = procmgr_timer_tolerance ( 0, NULL, &p_tolerance);
   if (rc == -1)
   {
       // Handle the error
   }
   printf ("PROCESS_TOLERANT: %ld ns.\n", p_tolerance);
}

if (tinfo.flags &_NTO_TI_TOLERANT) {
   printf ("TOLERANT");
   tolerance = tinfo.otime.interval_nsec;

   // If the tolerance is less than the output of ClockPeriod(),
   // the timer is a high-resolution one.
   if ((tolerance > 0) && (tolerance < period.nsec)) {
      printf (" (high-resolution): %d ns.\n", tolerance);
   } else {
      printf (": %d ns.\n", tolerance);
   }
}

For a more detailed example, see the entry for TimerInfo() in the C Library Reference.