Clock and timer services

Clock services are used to maintain the time of day, which is in turn used by the kernel timer calls to implement interval timers.

Valid dates on a BlackBerry 10 OS system range from January 1970 to at least 2038. The time_t data type is an unsigned 32-bit number, which extends this range for many applications through 2106. The kernel itself uses unsigned 64-bit numbers to count the nanoseconds since January 1970, and so can handle dates through 2554. If your system must operate past 2554 and there's no way for the system to be upgraded or modified in the field, you have to take special care with system dates (contact us for help with this).

The ClockTime() kernel call allows you to get or set the system clock specified by an ID (CLOCK_REALTIME), which maintains the system time. When set, the system time increments by some number of nanoseconds based on the resolution of the system clock. This resolution can be queried or changed using the ClockPeriod() call.

Within the system page, an in-memory data structure, there's a 64-bit field (nsec) that holds the number of nanoseconds since the system was booted. The nsec field is always monotonically increasing and is never affected by setting the current time of day via ClockTime() or ClockAdjust().

The ClockCycles() function returns the current value of a free-running 64-bit cycle counter. This is implemented on each processor as a high-performance mechanism for timing short intervals. For example, on Intel x86 processors, an opcode that reads the processor's time-stamp counter is used. On a Pentium processor, this counter increments on each clock cycle. A 100 MHz Pentium would have a cycle time of 1/100,000,000 seconds (10 nanoseconds). Other CPU architectures have similar instructions.

On processors that don't implement such an instruction in hardware, the kernel emulates one. This provides a lower time resolution than if the instruction is provided (838.095345 nanoseconds on an IBM PC-compatible system).

In all cases, the SYSPAGE_ENTRY(qtime)->cycles_per_sec field gives the number of ClockCycles() increments in one second.

The ClockPeriod() function allows a thread to set the system timer to some multiple of nanoseconds; the OS kernel does the best it can to satisfy the precision of the request with the hardware available.

The interval selected is always rounded down to an integral of the precision of the underlying hardware timer. Of course, setting it to an extremely low value can result in a significant portion of CPU performance being consumed servicing timer interrupts.

Microkernel call POSIX call Description
ClockTime() clock_gettime(), clock_settime() Get or set the time of day (using a 64-bit value in nanoseconds ranging from 1970 to 2554).
ClockAdjust() N/A Apply small time adjustments to synchronize clocks.
ClockCycles() N/A Read a 64-bit free-running high-precision counter.
ClockPeriod() clock_getres() Get or set the period of the clock.
ClockId() clock_getcpuclockid(), pthread_getcpuclockid() Return an integer that's passed to ClockTime() as a clockid_t.

The kernel runs in a tickless mode to reduce power consumption, but this is a bit of a misnomer. The system still has clock ticks, and everything runs as normal unless the system is idle. Only when the system goes completely idle does the kernel turn off clock ticks, and in reality what it does is slow down the clock so that the next tick interrupt occurs just after the next active timer is to fire, so that the timer fires immediately.

Time correction

To facilitate applying time corrections without having the system experience abrupt steps in time (or even having time jump backwards), the ClockAdjust() call provides the option to specify an interval over which the time correction is to be applied. This has the effect of speeding or retarding time over a specified interval until the system has synchronized to the indicated current time. This service can be used to implement network-coordinated time averaging between multiple nodes on a network.

Timers

The BlackBerry 10 OS 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 (that is, n nanoseconds from now)
  • Cyclical (that is, 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 one time 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 BlackBerry 10 OS-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 is expired before the service is even requested. The application then ends up requesting the service with an already lapsed timeout in effect (that is, 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 10 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 atomically enables 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 is disabled and control is 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 the time remaining on an interval timer
TimerInfo() timer_getoverrun() Get the 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 Clocks, timers, and getting a kick every so often.

Last modified: 2015-05-07



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus