The timer service is an extension of the timeout mechanism, enabling user and supervisor actors to set up call backs in a flexible manner. Both one-shot and periodic timers are provided. timeout notification is achieved through user-provided handler threads which are woken in the application actor.
The timer facility uses the principle of a timer object within the actor. Timer objects may be created, set and deleted dynamically. After being created, the timer objects are addressed by local identifiers within the context of the actor, and are deleted automatically when the actor terminates.
The application creates one or more threads dedicated to timer notification handling by declaring themselves ready to handle these types of events. The relationship between a timer object and a thread (or set of threads) is established trough a threadPool object that is used to block threads waiting for expiration of a timer.
Therefore, the basic mechanism for dealing with timers is:
Allocate and initialize a threadPool object.
Create one thread which will block on the threadPool object.
Create a timer associated with the threadPool object.
Set the timer (effectively arming it).
The second and third steps may be performed in any order. When timer expiration occurs, the dedicated thread is unblocked so that it may now execute any operation that should be performed at timer expiration. For example, the thread may print a warning message, re-arm the timer (unless it is a periodic timer), and block itself again. As is usually the case with the ChorusOS operating system data structures, these threadPool objects must be pre-allocated by the application.
A threadPool object is initialized as follows:
#include <etimer> int timerThreadPoolInit(KnThreadPool* threadPool);
A timer can then be created as follows:
int timerCreate(KnCap* actorCap, int clockType, KnThreadPool* threadPool, void* cookie, int* timerLi);
The previous code fragment creates a timer object in the actor defined by the actorCap parameter. Applications usually use K_MYACTOR. When the timer is armed and reaches expiration, one of the threads blocked on the threadPool object is selected and awakened. This thread is passed the cookie parameter of the timerCreate(2K) call. If successful, timerCreate(2K) returns the local identifier of the created timer in the location defined by the timerLi parameter. At the time of this printing the only clock type supported is K_CLOCK_REALTIME, which corresponds to the time returned by sysTime(2K).
A thread can block itself on a threadPool object through the following call:
#include <etimer> int timerThreadPoolWait(KnThreadPool* threadPool, void** cookie, int* overrun, KnTimeVal* waitLimit);
The threadPool object must have been initialized previously.
The timerThreadPoolWait(2K) call blocks the invoking thread until a timer associated with threadPool expires or until the waitLimit condition is reached. Upon timer expiration, the thread returns from this call, and the cookie field has been updated with the value associated with the timer.
The overrun counter is used to indicate to the thread that the timeout notification has been delayed (in this case the overrun value is 1), or that a number of timeout notifications have been lost (in this case the overrun value is always greater than 1).
A timer may be armed with:
#include <etimer> int timerSet(KnCap* actorCap, int timerLi, int flag, KnITimer* new, KnITimer* old);
The timerSet() call arms the timer defined by the first two parameters where timerLi is the timer identifier returned by timerCreate(2K). The timerSet(2K) call enables the specification of the timeout with a relative or an absolute time (using the flag parameter). The timeout can be specified using the new parameter, a structure containing the following fields:
KnTimeVal ITmValue. This field specifies the specific time at which the timeout will be invoked for the first time.
If the flag is set to K_TIMER_ABSOLUTE, the time value is an absolute time (in relation to time as managed by the sysTime() service).
If the flag is set to K_TIMER_INTERVAL, the time value is a delay relative to the current time.
KnTimeVal ITmReload. This field contains the subsequent interval for a periodic timer. If its value is 0, the timer will be a one-shot timer.
If the old parameter is non-NULL, the time remaining before the timer expires is returned at the location defined by old. If new is non-NULL and the timer has already been set, the current setting is cancelled and replaced with the new one. If the new time specified is 0, the current setting is simply cancelled. If new is set to NULL, the current setting specification is left unchanged.
For more information, refer to the timerThreadPoolInit(2K), timerCreate(2K), timerSet(2K), timerSet(2K), timerThreadPoolWait(2K) man pages.
The following example illustrates the use of timer services for both user and supervisor actors.
(file: opt/examples/progov/timers.c) #include <stdio.h> #include <stdlib.h> #include <chorus.h> #include <etimer.h> KnThreadPool samplePool; int periodic; int oneShot; int periodicLid; int oneShotLid; #define USER_STACK_SIZE (1024 * sizeof(long)) KnSem sampleSem; /* Semaphore allocated as global variable */ int childCreate(KnPc entry) { KnActorPrivilege actorP; KnDefaultStartInfo_f startInfo; char* userStack; int childLid = -1; int res; startInfo.dsType = K_DEFAULT_START_INFO; startInfo.dsSystemStackSize = K_DEFAULT_STACK_SIZE; res = actorPrivilege(K_MYACTOR, &actorP, NULL); if (res != K_OK) { printf("Cannot get the privilege of the actor, error %d\n", res); exit(1); } if (actorP == K_SUPACTOR) { startInfo.dsPrivilege = K_SUPTHREAD; } else { startInfo.dsPrivilege = K_USERTHREAD; } if (actorP != K_SUPACTOR) { userStack = malloc(USER_STACK_SIZE); if (userStack == NULL) { printf("Cannot allocate user stack\n"); exit(1); } startInfo.dsUserStackPointer = userStack + USER_STACK_SIZE; } startInfo.dsEntry = entry; res = threadCreate(K_MYACTOR, &childLid, K_ACTIVE, 0, &startInfo); if (res != K_OK) { printf("Cannot create the thread, error %d\n", res); exit(1); } return childLid; } void timerWait(int myThLi) { } void sampleThread() { int myThLi; int res; void* cookie; int overrun; KnITimer periodicTimer; KnTimeVal tv; myThLi = threadSelf(); printf("Thread %d started\n", myThLi); for(;;) { res = timerThreadPoolWait(&samplePool, &cookie, &overrun, K_NOTIMEOUT); if (res != K_OK) { printf("Cannot wait on thread pool, error %d\n", res); exit(1); } if (overrun != 0) { printf("Thread %d. We were late! overrun set to : %d\n", myThLi, overrun); } if (cookie == &periodic) { printf("Thread %d. Time is flying away!\n", myThLi); } else if (cookie == &oneShot) { printf("Thread %d. Isn't it time to go home?\n", myThLi); periodicTimer.ITmValue.tmSec = 0; /* seconds */ periodicTimer.ITmValue.tmNSec = 0; /* nanoseconds */ periodicTimer.ITmReload.tmSec = 0; /* seconds */ periodicTimer.ITmReload.tmNSec = 0; /* nanoseconds */ res = timerSet(K_MYACTOR, periodicLid, NULL, &periodicTimer, NULL); if (res != K_OK) { printf("Cannot cancel periodic timer, error %d\n", res); exit(1); } /* * Periodic timer is cancelled * Get current time, * Wait for a short while (3 seconds) and quit */ res = sysTime(&tv); if (res != K_OK) { printf("Cannot get system time, error %d\n", res); exit(1); } printf("Current system time is %d seconds\n", tv.tmSec); printf("No more periodic messages should be printed now!\n"); K_MILLI_TO_TIMEVAL(&tv, 3000); (void) threadDelay(&tv); /* We are all done ! */ exit(0); } else { printf("Spurious timer!\n"); } } /* for() */ } int main(int argc, char** argv, char** envp) { int res; KnTimeVal tv; int thLi1; int thLi2; KnITimer periodicTimer; KnITimer oneShotTimer; res = timerThreadPoolInit(&samplePool); if (res != K_OK) { printf("Cannot initialize thread pool, error %d\n", res); exit(1); } res = timerCreate(K_MYACTOR, K_CLOCK_REALTIME, &samplePool, &periodic, &periodicLid); if (res != K_OK) { printf("Cannot create periodic timer, error %d\n", res); exit(1); } res = timerCreate(K_MYACTOR, K_CLOCK_REALTIME, &samplePool, &oneShot, &oneShotLid); if (res != K_OK) { printf("Cannot create one shot timer, error %d\n", res); exit(1); } thLi1 = childCreate((KnPc)sampleThread); thLi2 = childCreate((KnPc)sampleThread); res = sysTime(&tv); if (res != K_OK) { printf("Cannot get system time, error %d\n", res); exit(1); } printf("Current system time is %d seconds\n", tv.tmSec); periodicTimer.ITmValue.tmSec = 1; /* seconds */ periodicTimer.ITmValue.tmNSec = 0; /* nanoseconds */ periodicTimer.ITmReload.tmSec = 1; /* seconds */ periodicTimer.ITmReload.tmNSec = 0; /* nanoseconds */ res = timerSet(K_MYACTOR, periodicLid, NULL, &periodicTimer, NULL); if (res != K_OK) { printf("Cannot arm periodic timer, error %d\n", res); exit(1); } oneShotTimer.ITmValue.tmSec = tv.tmSec + 30; /* seconds */ oneShotTimer.ITmValue.tmNSec = 0; /* nanoseconds */ oneShotTimer.ITmReload.tmSec = 0; /* seconds */ oneShotTimer.ITmReload.tmNSec = 0; /* nanoseconds */ res = timerSet(K_MYACTOR, oneShotLid, K_TIMER_ABSOLUTE, &oneShotTimer, NULL); if (res != K_OK) { printf("Cannot arm one shot timer, error %d\n", res); exit(1); } res = threadDelete(K_MYACTOR, K_MYSELF); if (res != K_OK) { printf("Cannot kill myself, error %d\n", res); exit(1); } return 0; }
The previous example includes the following step:
The main thread sets up everything that is required, so that the two newly-created threads will respond to a single periodic timer of one second for a duration of thirty seconds.
The thirty-second period is bounded by a one-shot timer handled by the same pool of two threads.
Before starting, the current system time is printed.
When the thirty second timer has elapsed, the periodic timer is cancelled and the current system time is displayed again.
A small delay occurs before the actor terminates, enabling you to check that the periodic timer has been cancelled correctly.