QNX Neutrino directly provides the full set of POSIX timer functionality. Since these timers are quick to create and manipulate, they're an inexpensive resource in the kernel.
The POSIX timer model is quite rich, providing the ability to have the timer expire on:
- an absolute date
- a relative date (i.e. n nanoseconds from now)
- cyclical (i.e. every n nanoseconds)
The cyclical mode is very significant, because the most common use of timers tends to be as a periodic source of events to kick a thread into life to do some processing and then go back to sleep until the next event. If the thread had to re-program the timer for every event, there would be the danger that time would slip unless the thread was programming an absolute date. Worse, if the thread doesn't get to run on the timer event because a higher-priority thread is running, the date next programmed into the timer could be one that has already elapsed!
The cyclical mode circumvents these problems by requiring that the thread set the timer once and then simply respond to the resulting periodic source of events.
Since timers are another source of events in the OS, they also make use of its event-delivery system. As a result, the application can request that any of the Neutrino-supported events be delivered to the application upon occurrence of a timeout.
An often-needed timeout service provided by the OS is the ability to specify the maximum time the application is prepared to wait for any given kernel call or request to complete. A problem with using generic OS timer services in a preemptive realtime OS is that in the interval between the specification of the timeout and the request for the service, a higher-priority process might have been scheduled to run and preempted long enough that the specified timeout will have expired before the service is even requested. The application will then end up requesting the service with an already lapsed timeout in effect (i.e. no timeout). This timing window can result in hung processes, inexplicable delays in data transmission protocols, and other problems.
alarm(...); ... ... ← Alarm fires here ... blocking_call();
Our solution is a form of timeout request atomic to the service request itself. One approach might have been to provide an optional timeout parameter on every available service request, but this would overly complicate service requests with a passed parameter that would often go unused.
BlackBerry PlayBook OS provides a TimerTimeout() kernel call that allows an application to specify a list of blocking states for which to start a specified timeout. Later, when the application makes a request of the kernel, the kernel will atomically enable the previously configured timeout if the application is about to block on one of the specified states.
Since the OS has a very small number of blocking states, this mechanism works very concisely. At the conclusion of either the service request or the timeout, the timer will be disabled and control will be given back to the application.
TimerTimeout(...); ... ... ... blocking_call(); ... ← Timer atomically armed within kernel
|Microkernel call||POSIX call||Description|
|TimerAlarm()||alarm()||Set a process alarm.|
|TimerCreate()||timer_create()||Create an interval timer.|
|TimerDestroy()||timer_delete()||Destroy an interval timer.|
|TimerInfo()||timer_gettime()||Get time remaining on an interval timer.|
|TimerInfo()||timer_getoverrun()||Get number of overruns on an interval timer.|
|TimerSettime()||timer_settime()||Start an interval timer.|
|TimerTimeout()||sleep() , nanosleep() , sigtimedwait() , pthread_cond_timedwait() , pthread_mutex_trylock()||Arm a kernel timeout for any blocking state.|
For more information, see the Clocks, Timers, and Getting a Kick Every So Often chapter of Getting Started with QNX Neutrino.