Every Maté handler has an associated
context component. The component is responsible for
allocating a Maté execution context structure
for the handler to run on, and triggering the
context to run when an appropriate TinyOS event
occurs. Like functions, context components usually
have three files: a context description file
(.cdf), a module implementing the context,
and a configuration that wires the module to all of
the other components it needs.
A CDF must have a CONTEXT element that describes
the context. This element must have three tags,
DESC, ID, and NAME. DESC is a descriptive string
which the VMBuilder GUI displays to the
user. VMBuilder and Scripter use the NAME tag to
uniquely identify a context. VMBuilder uses the
value of the NAME tag to generate unique identifier
constants referring to the context and its
handler. For example, if NAME=EXAMPLE,
VMBuilder generates the constants
MATE_CONTEXT_EXAMPLE and
MATE_HANDLER_EXAMPLE. When Scripter injects
a new piece of code, it uses the HANDLER context to
identify which handler the code corresponds to, so
the VM can install it properly. When a VM sends an
error message to the user, Scripter uses the CONTEXT
constants to map the unique ID to the name of the
context that triggered the error.
The ID element is currently unused, but will be
used in the future. The issue with using unique
IDs is that two different VMs with the same
contexts cannot share code, as they might have
different IDs for the same context. Explicit,
global IDs for contexts solves this problem. A
node will map the global ID to its local ID for
the context.
Contexts can have functions associated with
them. For example, the Timer contexts have the
settimer functions to control their firing
rate, while the Trigger context has the
trigger and triggerbuf
functions. A context description file, in addition
to containing a CONTEXT element, can contain
FUNCTION elements which VMBuilder will automatically
include when the context is incorporated into a
VM. For example, this is
lib/VM/contexts/Timer0Context.cdf:
<CONTEXT name="Timer0" desc="A periodic timer." id=7>
<FUNCTION name=SETTIMER0 opcode=settimer0 numparams=1 param1=value returnval=FALSE desc="Takes a single parameter, the interval (in tenths of a second) between timer firings for the Timer 0 context. Calling with a time of zero stops the timer.">
Context modules must use three interfaces:
MateContextSynch, for interacting with the
Mat´ scheduler, MateHandlerStore, for
knowing when new code has arrived, and
MateEngineStatus, for knowing when the VM
has rebooted. Whenever new code arrives or the VM
reboots, a context component must halt its context
by calling MateContextSynch.halt().
Currently, the VM core signals reboot whenever any
new code arrives, making one of the calls to halt
redundant. However, better installation-time
analysis may change this in the future, so a
component should handle both, just to be
sure. Contexts need to initialize their handler with
MateHandlerStore when TinyOS boots, so
context components usually provide a
StdControl interface.
In addition to these standard interfaces,
contexts usually provide or use a few others, which
control when the context executes. For example, the
Timer0 context uses and provides Timer interfaces
for controllingits time. The signature of
Timer0ContextM is:
module Timer0ContextM {
provides {
interface StdControl;
interface Timer;
}
uses {
interface StdControl as SubControlTimer;
interface Timer as ClockTimer;
interface MateContextSynch as Synch;
interface MateHandlerStore as HandlerStore;
interface MateEngineStatus as EngineStatus;
}
}
The provided Timer interface allows the
settimer0 function component to manipulate
Timer0's Timer; it forwards calls to the underlying
ClockTimer. When ClockTimer fires,
Timer0ContextM submits its context to the
Maté scheduler, if it is not already
running.
We'll step through the implementation of
Timer0COntextM, one part at a time. The source file
resides int tos/lib/VM/contexts. You've
already seen the module signature. The module
allocates a MateContext, which it
initializes in StdControl.init():
implementation {
MateContext timerContext;
command result_t StdControl.init() {
result_t rval = call SubControlTimer.init();
timerContext.which = MATE_CONTEXT_TIMER0;
timerContext.rootHandler = MATE_HANDLER_TIMER0;
rval &= call HandlerStore.initializeHandler();
call Synch.initializeContext(&timerContext);
return rval;
}
command result_t StdControl.start() {
return call SubControlTimer.start();
}
command result_t StdControl.stop() {
call ClockTimer.stop();
return call SubControlTimer.stop();
}
init() first initializes the
underlying Timer component, then sets two fields
in the context. The first field is the context
identifier, which is the MATE_CONTEXT
constant corresponding to the context name. The
second field is the handler identifier, which
refers to what code handler the VM should run when
the context starts executing. Next,
init() tells HandlerStore to
initialize its handler. The component providing
HandlerStore provides it as a
parameterized interface: the parameter is the
handler ID. This allows it to know which handler
to initialize. Finally, init()
initializes the actual context with
Synch.initializeContext: the two fields,
which and rootHandler must be
set before this is called. start() just
starts the underlying Timer component (but not the
ClockTimer itself, which
settimer0 controls). stop,
however, stops both the component and the
ClockTimer.
Next is the ClockTimer.fired() event,
and the task it uses to run the context:
task void ClockEventTask() {
if (timerContext.state == MATE_STATE_HALT) {
dbg(DBG_USR1, "VM: Timer 0 context running.\n");
timerContext.currentHandler = timerContext.rootHandler;
call Synch.initializeContext(&timerContext);
call Synch.resumeContext(&timerContext, &timerContext);
}
else {
// drop the event
}
}
event result_t ClockTimer.fired() {
post ClockEventTask();
return SUCCESS;
}
When the underlying ClockTimer fires,
the Timer0ContextM posts
ClockEventTask, which ignores the event
if the context is already running. Otherwise, it
re-initializes the context (e.g., setting the
program counter to the first instruction) and
submits it to the Maté scheduler with
resumeContext.
As it uses the MateHandlerStore and
MateEngineStatus interfaces, a context
component must handle two events, when new code
arrives and when the VM reboots. In both cases,
the component should halt the context if it is not
already halted.
event void HandlerStore.handlerChanged() {
dbg(DBG_USR3, "Timer0Context: Handler changed.\n");
if (timerContext.state != MATE_STATE_HALT) {
call Synch.haltContext(&timerContext);
}
}
event void EngineStatus.rebooted() {
dbg(DBG_USR1, "Timer0Context: VM rebooted.\n");
if (timerContext.state != MATE_STATE_HALT) {
call Synch.haltContext(&timerContext);
}
}
Finally, Timer0ContextM forwards calls
made to its provided Timer to the
ClockTimer it uses:
command result_t Timer.start(char type, uint32_t interval) {
return call ClockTimer.start(type, interval);
}
command result_t Timer.stop() {
return call ClockTimer.stop();
}
default event result_t Timer.fired() {
return SUCCESS;
}
The Timer0 context includes the
settimer0 function, which calls the
Timer commands on
Timer0ContextM. Another approach is to
have a context component provide instances of the
MateBytecode interface. The function
configurations can then just wire to these
implementations. For example, instead of having a
OPsettimer0M module,
Timer0ContextM could include
provides interface MateBytecode as SetTimer;
and the OPsettimer0 configuration
could just wire to it:
MateBytecode = Timer0ContextM.SetTimer;
This approach is useful if a function needs to
access context state. For example, the Trigger
context allows a program to pass a buffer to the
Trigger handler as a parameter to the
trigger function, which also causes the
context to run. The function copies the buffer
into the Trigger component (so the caller can
continue to access its buffer), which can be
accessed with the triggerbuf function. In
this case, the Trigger component implements both
functions. The trigger function copies
the passed buffer into a component variable, and
the triggerbuf function pushes that
buffer onto the operand stack.
Unlike functions, context configurations do not
provide an interface; they self-wire themselves to
the Maté core. VMBuilder has to wire
functions because it is responsible for generating
the instruction set of a VM; contexts, in
contrast, have no corresponding values that
VMBuilder must generate, so can just wire
themselves. For example, this is
Timer0Context.nc:
includes Mate;
configuration Timer0Context {
provides interface Timer;
}
implementation {
components Timer0ContextM as Context, TimerC;
components MContextSynchProxy as ContextSynch;
components MateEngine as VM;
components MHandlerStoreProxy as Store;
Timer = Context;
Context.SubControlTimer -> TimerC;
Context.ClockTimer -> TimerC.Timer[unique("Timer")];
Context.Synch -> ContextSynch;
Context <- VM.SubControl;
Context.HandlerStore -> Store.HandlerStore[MATE_HANDLER_TIMER0];
Context.EngineStatus -> VM;
}
Just putting a context component in the
top-level Maté configuration component list
will include it and all of its wirings.
|