The ChorusOS operating system offers five time management services:
Tick service
Date service
Time-out service
Timer service
Virtual time and virtual time-out service
The configuration of your ChorusOS operating system determines which services are available.
The following time management services are available:
The tick
service enables the system
to manage the clock, counting ticks since the boot of the system. Thus the
only time available is the time elapsed since the last reboot.
The date
service enables
the ChorusOS operating system to maintain a current date, usually expressed
in seconds since the 01/01/1970. Calls to set and get the time of day are
available, through standard C libraries ctime
and localtime
, and are not detailed in this document.
The time-out
service enables
supervisor actors to set up time-outs. A time-out may be roughly described
as a callback which will be performed when a given delay has expired. Callbacks
are performed using a special invocation mechanism (called Local Access Point
or LAP) reserved for supervisor actors.
The timer
service is
an extension of the time-out mechanism, enabling user and supervisor actors
to set up call backs in a more flexible fashion.
The virtual time and time-out service allows you to measure the CPU time used by threads, and to define handlers which will be called if a per-thread or per-actor CPU quota is reached.
Table 9-1 shows which services are available for a given configuration:
Table 9-1 Time Management Service AvailabilityService | Availability |
---|---|
tick | always available |
date | configured with DATE |
time-out | always available |
timer | configured with TIMER |
virtual time | configured with VTIMER |
An actor, whether user or supervisor, may get the time elapsed since the last reboot through the following system call:
#include <exec/chTime.h> int sysTime(KnTimeVal* time);
This will fill in the time data structure which is built from two fields:
tmSec which indicates the number of whole seconds elapsed since the last reboot
tmNSec which indicates the number of nanoseconds
The resolution of the value depends on the platform on which the system is running, and may be obtained by a call to:
#include <chorus.h> int sysTimeGetRes(KnTimeVal* resolution);
The time value returned at the location defined by the resolution parameter represents the smallest possible difference between two distinct values of the system time.
This feature provides timer services for both user and supervisor actors. One-shot as well as periodic timers are provided. Time-out notification is achieved through user-provided handler threads which are woken up in the application actor.
The timer facility uses the concept of a timer object within the actor. These timer objects may be created, deleted and set dynamically. Once created, they are addressed by local identifiers within the context of the actor and are deleted automatically when the actor terminates.
The application is expected to create 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 a set of threads) is established through a threadPool object which is used to block threads waiting for the expiration of a timer.
Thus, 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 above threadPool object.
Set the timer (effectively arm it).
The second and third steps may take place in any order. When timer expiration occurs, the dedicated thread will be unblocked so that it may now perform any operation which should be done due to timer expiration. For example, it may print a warning message, re-arm the timer (unless it was a periodic timer), and block itself again. As usual 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/chEtimer.h> int timerThreadPoolInit(KnThreadPool* threadPool);
A timer may then be created as follows:
#include <etimer/chEtimer.h> int timerCreate(KnCap* actorCap, int clockType, KnThreadPool* threadPool, void* cookie, int* timerLi);
This creates a timer object in the actor defined
by the actorCap parameter. Applications will usually use
K_MYACTOR
. When the timer is armed and reaches expiration,
one of the threads blocked on the threadPool object will
be selected and awakened. This thread will be passed the cookie
parameter of the timerCreate() call. When successful, timerCreate() returns the local identifier of the created timer at
the location defined by the timerLi parameter. The only
clock type currently supported is K_CLOCK_REALTIME
,
and corresponds to the time returned by sysTime().
A thread may block itself on a threadPool object through the following call:
#include <etimer/chEtimer.h> int timerThreadPoolWait(KnThreadPool* threadPool, void** cookie, int* overrun, KnTimeVal* waitLimit);
The threadPool object must have been previously initialized. timerThreadPoolWait() blocks the invoking thread until a timer associated with threadPool expires or until the waitLimit condition is reached. Upon timer expiration, the thread will return from this call, and the cookie field will have been updated with the value associated with the timer.
The overrun counter is used to indicate to the thread that either the time-out notification has been delayed (in this case the overrun value is 1) or that a number of time-out notifications have been lost (in this case the overrun value is strictly greater than 1).
A timer may be armed with:
#include <etimer/chEtimer.h> int timerSet(KnCap* actorCap, int timerLi, int flag, KnITimer* new, KnITimer* old);
This call arms the timer defined by the first two parameters where timerLi is the timer identifier as returned by timerCreate(). timerSet() allows the specification of the time-out using either a relative or an absolute time using the flag parameter. The time-out is specified using the new parameter which is a structure containing the following fields:
KnTimeVal ITmValue. This field specifies at what time the time-out will occur for the first (and maybe only) time.
If the flag is set to K_TIMER_ABSOLUTE,
the time value is an absolute time (in terms of 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 timer expiration 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 will simply be cancelled. If new is set to null, the current setting specification is left unchanged.
Refer to the timerThreadPoolInit(2K), timerCreate(2K), timerSet(2K), timerThreadPoolWait(2K), and sysTime(2K) man pages.
The following example illustrates the use of timer services for both user and supervisor actors.
(file: progov/timers.c) #include <stdio.h> #include <stdlib.h> #include <chorus.h> #include <etimer/chEtimer.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) { /* do nothing */ } 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); 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 suicide myself, error %d\n", res); exit(1); } return 0; }
The main thread sets up everything that is needed, so that two 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 printed again.
A small delay has been added before the actor terminates to check that the periodic timer has been cancelled correctly.