Now that we've seen the basics of timers, we'll look at a few advanced topics:
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.
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:
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.
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:
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.
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).
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).
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 sigevent — TimerTimeout() 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));
}
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.
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.