Advanced topics

Now that we've seen the basics of timers, we'll look at a few advanced topics:

  1. The CLOCK_SOFTTIME and CLOCK_MONOTONIC timer types, and
  2. Kernel timeouts

Other clock sources

We've seen the clock source CLOCK_REALTIME, and mentioned that a POSIX conforming implementation may supply as many different clock sources as it feels like, provided that it at least provides CLOCK_REALTIME.

What is a clock source? It's an abstract source of timing information. If you want to put it into real life concepts, your personal watch is a clock source; it measures how fast time goes by. Your watch has a different level of accuracy than someone else's watch. You may forget to wind your watch, or get it new batteries, and time may seem to freeze for a while. Or, you may adjust your watch, and all of a sudden time seems to jump. These are all characteristics of a clock source.

Under BlackBerry 10 OS, CLOCK_REALTIME is based off of the current time of day clock that BlackBerry 10 OS provides. (In the following examples, we refer to this as BlackBerry 10 OS time.) This means that if the system is running, and suddenly someone adjusts the time forward by 5 seconds, the change may or may not adversely affect your programs (depending on what you're doing). Let's look at a sleep (30); call:

Real time BlackBerry 10 OS time Activity
11:22:05 11:22:00 sleep (30);
11:22:15 11:22:15 Clock gets adjusted to 11:22:15; it was 5 seconds too slow!
11:22:35 11:22:35 sleep (30); wakes up

Beautiful! The thread did exactly what you expected: at 11:22:00 it went to sleep for thirty seconds, and at 11:22:35 (thirty elapsed seconds later) it woke up. Notice how the sleep() appeared to sleep for 35 seconds, instead of 30; in real, elapsed time, though, only 30 seconds went by becauseBlackBerry 10 OS's clock got adjusted ahead by five seconds (at 11:22:15).

The kernel knows that the sleep() call is a relative timer, so it takes care to ensure that the specified amount of real time elapses.

Now, what if, on the other hand, we had used an absolute timer, and at 11:22:00 in BlackBerry 10 OS time told the kernel to wake us up at 11:22:30?

Real time BlackBerry 10 OS time Activity
11:22:05 11:22:00 Wake up at 11:22:30
11:22:15 11:22:15 Clock gets adjusted as before
11:22:30 11:22:30 Wakes up

This too is just like what you expect—you want to be woken up at 11:22:30, and (in spite of adjusting the time) you are. However, in real time, the timer expired after 25 seconds, instead of the full 30. Depending on the application, this could be a problem.

CLOCK_MONOTONIC

The POSIX people thought about this, and the solution they came up with was another clock source called CLOCK_MONOTONIC. When you create a timer object with timer_create(), you can tell it which clock source to use. The way CLOCK_MONOTONIC works is that its timebase is never adjusted. The impact of that is that regardless of what time it is in the real world, if you base a timer on CLOCK_MONOTONIC and add 30 seconds to it (and then do whatever adjustments you want to the time), the timer expires in 30 elapsed seconds.

The clock source CLOCK_MONOTONIC has the following characteristics:

  • Always increasing count
  • Based on real time
  • Starts at zero
Note: The important thing about the clock starting at zero is that this is a different epoch (or base) than CLOCK_REALTIME's epoch of Jan 1 1970, 00:00:00 GMT. So, even though both clocks run at the same rate, their values are not interchangeable.

Functions that don't let you choose the clock source—such as pthread_mutex_timedlock() —use CLOCK_REALTIME. This is built in to the function and isn't something that you can change.

So what does CLOCK_SOFTTIME do?

If we wanted to sort our clock sources by hardness we'd have the following ordering. You can think of CLOCK_MONOTONIC as being a freight train — it doesn't stop for anyone. Next on the list is CLOCK_REALTIME, because it can be pushed around a bit (as we saw with the time adjustment). Finally, we have CLOCK_SOFTTIME, which we can push around a lot.

The main use of CLOCK_SOFTTIME is for things that are soft — things that aren't going to cause a critical failure if they don't get done. CLOCK_SOFTTIME is active only when the CPU is running. When the CPU is powered down due to Power Management detecting that nothing is going to happen for a little while, CLOCK_SOFTTIME gets powered down as well!

Here's a timing chart showing the three clock sources:

Real time BlackBerry 10 OS time Activity
11:22:05 11:22:00 Wake up at now + 00:00:30
11:22:15 11:22:15 Clock gets adjusted as before
11:22:20 11:22:20 Power management turns off CPU
11:22:30 11:22:30 CLOCK_REALTIME wakes up
11:22:35 11:22:35 CLOCK_MONOTONIC wakes up
11:45:07 11:45:07 Power management turns on CPU, and CLOCK_SOFTTIME wakes up

There are a few things to note here:

  • We precomputed our wakeup time as now plus 30 seconds and used an absolute timer to wake us up at the computed time. This is different from waking up in 30 seconds using a relative timer.
  • Note that for convenience of putting the example on one time-line, we've lied a little bit. If the CLOCK_REALTIME thread did indeed wake up, (and later the same for CLOCK_MONOTONIC) it would have caused us to exit out of power management mode at that time, which would then cause CLOCK_SOFTTIME to wake up.

When CLOCK_SOFTTIME over-sleeps, it wakes up as soon as it's able — it doesn't stop timing while the CPU is powered down, it's just not in a position to wake up until after the CPU powers up. Other than that, CLOCK_SOFTTIME is just like CLOCK_REALTIME.

Using different clock sources

To specify one of the different clock source, use a POSIX timing function that accepts a clock ID. For example:

#include <time.h>

int
clock_nanosleep (clockid_t clock_id,
                 int flags,
                 const struct timespec *rqtp,
                 struct timespec *rmtp);

The clock_nanosleep() function accepts the clock_id parameter (telling it which clock source to use), a flag (which determines if the time is relative or absolute), a requested sleep time parameter (rqtp), as well as a pointer to an area where the function can fill in the amount of time remaining (in the rmtp parameter, which can be NULL if you don't care).

Kernel timeouts

BlackBerry 10 OS lets you have a timeout associated with all kernel blocking states. We talked about the blocking states in Processes and threads. Most often, you want to use this with message passing; a client sends a message to a server, but the client won't want to wait forever for the server to respond. In that case, a kernel timeout is suitable. Kernel timeouts are also useful with the pthread_join() function. You might want to wait for a thread to finish, but you might not want to wait too long.

Here's the definition for the TimerTimeout() function call, which is the kernel function responsible for kernel timeouts:

#include <sys/neutrino.h>

int
TimerTimeout (clockid_t id,
              int flags,
              const struct sigevent *notify,
              const uint64_t *ntime,
              uint64_t *otime);

This says that TimerTimeout() returns an integer (a pass/fail indication, with -1 meaning the call failed and set errno, and zero indicating success). The time source (CLOCK_REALTIME, and so on) is passed in id, and the flags parameter gives the relevant kernel state or states. The notify should always be a notification event of type SIGEV_UNBLOCK, and the ntime is the relative time when the kernel call should timeout. The otime parameter indicates the previous value of the timeout — it's not used in the vast majority of cases (you can pass NULL).

Note: It's important to note that the timeout is armed by TimerTimeout(), and triggered on entry into one of the kernel states specified by flags. It is cleared upon return from any kernel call. This means that you must re-arm the timeout before each and every kernel call that you want to be timeout-aware. You don't have to clear the timeout after the kernel call; this is done automatically.

Kernel timeouts with pthread_join()

The simplest case to consider is a kernel timeout used with the pthread_join() call. Here's how you set it up:

/*
 * part of tt1.c
*/

#include <sys/neutrino.h>

// 1 billion nanoseconds in a second
#define SEC_NSEC 1000000000LL 

int
main (void) // ignore arguments
{
    uint64_t        timeout;
    struct sigevent event;
    int             rval;

    …
    // set up the event -- this can be done once
    
    // This or event.sigev_notify = SIGEV_UNBLOCK:
    SIGEV_UNBLOCK_INIT (&event);

    // set up for 10 second timeout
    timeout = 10LL * SEC_NSEC;

    TimerTimeout (CLOCK_REALTIME, _NTO_TIMEOUT_JOIN,
                  &event, &timeout, NULL);

    rval = pthread_join (thread_id, NULL);
    if (rval == ETIMEDOUT) {
        printf ("Thread %d still running after 10 seconds!\n",
                thread_id);
    }
    …

(You can find the complete version of tt1.c in Sample programs.)

We used the SIGEV_UNBLOCK_INIT() macro to initialize the event structure, but we could have set the sigev_notify member to SIGEV_UNBLOCK ourselves. Even more elegantly, we can pass NULL as the struct sigeventTimerTimeout() understands this to mean that it should use a SIGEV_UNBLOCK.

If the thread (specified in thread_id) is still running after 10 seconds, then the kernel call times out — pthread_join() returns with an errno of ETIMEDOUT.

You can use another shortcut—by specifying a NULL for the timeout value (ntime in the formal declaration above), this tells the kernel not to block in the given state. This can be used for polling. (While polling is generally discouraged, you can use it quite effectively in the case of the pthread_join()—you can periodically poll to see if the thread you're interested in is finished yet. If not, you can perform other work.)

Here's a code sample showing a non-blocking pthread_join():

int
pthread_join_nb (int tid, void **rval)
{
    TimerTimeout (CLOCK_REALTIME, _NTO_TIMEOUT_JOIN, 
                  NULL, NULL, NULL);
    return (pthread_join (tid, rval));
}

Kernel timeouts with message passing

Things get a little trickier when you're using kernel timeouts with message passing. Recall from Message passing that the server may or may not be waiting for a message when the client sends it. This means that the client could be blocked in either the SEND-blocked state (if the server hasn't received the message yet), or the REPLY-blocked state (if the server has received the message, and hasn't yet replied). The implication here is that you should specify both blocking states for the flags argument to TimerTimeout(), because the client might get blocked in either state.

To specify multiple states, you can OR them together:

TimerTimeout (… _NTO_TIMEOUT_SEND | _NTO_TIMEOUT_REPLY, …);

This causes the timeout to be active whenever the kernel enters either the SEND-blocked state or the REPLY-blocked state. There's nothing special about entering the SEND-blocked state and timing out — the server hasn't received the message yet, so the server isn't actively doing anything on behalf of the client. This means that if the kernel times out a SEND-blocked client, the server doesn't have to be informed. The client's MsgSend() function returns an ETIMEDOUT indication, and processing has completed for the timeout.

However, as was mentioned in _NTO_CHF_UNBLOCK ), if the server has already received the client's message, and the client wants to unblock, there are two choices for the server. If the server has not specified _NTO_CHF_UNBLOCK on the channel it received the message on, then the client is unblocked immediately, and the server won't receive any indication that an unblock has occurred. Many servers always have the _NTO_CHF_UNBLOCK flag enabled. In that case, the kernel delivers a pulse to the server, but the client remains blocked until the server replies! As mentioned before, this is done so that the server has an indication that it should do something about the client's unblock request.

Summary

We've looked at BlackBerry 10 OS's time-based functions, including timers and how they can be used, as well as kernel timeouts. Relative timers provide some form of event in a certain number of seconds, while absolute timers provide this event at a certain time. Timers (and, generally speaking, the struct sigevent) can cause the delivery of a pulse, a signal, or a thread to start.

The kernel implements timers by storing the absolute time that represents the next event on a sorted queue, and comparing the current time (as derived by the timer tick interrupt service routine) against the head of the sorted queue. When the current time is greater than or equal to the first member of the queue, the queue is processed (for all matching entries) and the kernel dispatches events or threads (depending on the type of queue entry) and (possibly) reschedules.

To provide support for power-saving features, you should disable periodic timers when they're not needed — otherwise, the power-saving feature won't implement power saving, because it believes that there's something to do periodically. You can also use the CLOCK_SOFTTIME clock source, unless of course you actually wanted the timer to defeat the power saving feature.

Given the different types of clock sources, you have flexibility in determining the basis of your clocks and timer; from real, elapsed time through to time sources that are based on power management activities.