Tolerant and high-resolution timers

QNX SDP8.0Programmer's GuideDeveloper

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 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:
  • The tolerance may lengthen, but never shortens, the time before the timer expires.
  • Setting the tolerance doesn't affect the active/inactive status of the timer.
  • Timer tolerance is a QNX OS extension to the POSIX timer_settime() function. If you want to specify infinite timer tolerance, call TimerSettime() instead of timer_settime().
  • The startup code tries to determine the frequency of the hardware timer, but for greater accuracy you should specify the frequency by using the -f option; see startup-* options in the Utilities Reference.
  • Using high-resolution timers causes extra timer interrupts; as you use more high-resolution timers, the impact on system performance increases. The total impact will vary with interrupt frequency and the hardware characteristics of the system.
  • In order to set the tolerance to a value between 0 and the clock period, you need to have the PROCMGR_AID_HIGH_RESOLUTION_TIMER ability enabled. For more information, see procmgr_ability(). The flags for a high-resolution timer (which you can get by calling TimerInfo()) include _NTO_TI_HIGH_RESOLUTION.
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. This default tolerance is inherited across a fork() but not an exec*() or 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.

Page updated: