📄 modules and the tinyos execution model - tinyos documentation wiki.htm
字号:
users interface Boot;
}
implementation
{
uint8_t counter = 0;
event void Boot.booted()
{
call Timer0.startPeriodic( 250 );
}
event void Timer0.fired()
{
counter++;
call Leds.set(counter);
}
}
</PRE>
<P>Try to compile the application: nesC will throw an error, because the
configuration BlinkAppC is wiring to interfaces on BlinkC that no longer exist
(Timer1 and Timer2): </P><PRE>dark /root/src/tinyos-2.x/apps/BlinkSingle -5-> make micaz
mkdir -p build/micaz
compiling BlinkAppC to a micaz binary
ncc -o build/micaz/main.exe -Os -finline-limit=100000 -Wall -Wshadow -DDEF_TOS_AM_GROUP=0x7d -Wnesc-all -target=micaz
-fnesc-cfile=build/micaz/app.c -board=micasb -fnesc-dump=wiring -fnesc-dump='interfaces(!abstract())'
-fnesc-dump='referenced(interfacedefs, components)' -fnesc-dumpfile=build/micaz/wiring-check.xml BlinkAppC.nc -lm
In component `BlinkAppC':
BlinkAppC.nc:54: cannot find `Timer1'
BlinkAppC.nc:55: cannot find `Timer2'
make: *** [exe0] Error 1
</PRE>
<P>Open BlinkAppC and remove the two Timers and their wirings. Compile the
application: </P><PRE>mkdir -p build/micaz
compiling BlinkAppC to a micaz binary
ncc -o build/micaz/main.exe -Os -finline-limit=100000 -Wall -Wshadow -DDEF_TOS_AM_GROUP=0x7d -Wnesc-all -target=micaz
-fnesc-cfile=build/micaz/app.c -board=micasb -fnesc-dump=wiring -fnesc-dump='interfaces(!abstract())'
-fnesc-dump='referenced(interfacedefs, components)' -fnesc-dumpfile=build/micaz/wiring-check.xml BlinkAppC.nc -lm
compiled BlinkAppC to build/micaz/main.exe
2428 bytes in ROM
39 bytes in RAM
avr-objcopy --output-target=srec build/micaz/main.exe build/micaz/main.srec
avr-objcopy --output-target=ihex build/micaz/main.exe build/micaz/main.ihex
writing TOS image
</PRE>
<P>If you compare the ROM and RAM sizes with the unmodified Blink application,
you should see that they are a bit smaller: TinyOS is only allocating state for
a single timer, and there is event code for only one timer. </P><A
name=Interfaces.2C_Commands.2C_and_Events></A>
<H1><SPAN class=mw-headline>Interfaces, Commands, and Events</SPAN></H1>
<P>Go back to <TT>tinyos-2.x/apps/Blink</TT>. In lesson 1 we learned that if a
component uses an interface, it can call the interface's commands and must
implement handlers for its events. We also saw that the BlinkC component uses
the Timer, Leds, and Boot interfaces. Let's take a look at those interfaces:
</P><PRE>tos/interfaces/Boot.nc:
interface Boot {
event void booted();
}
</PRE><PRE>tos/interfaces/Leds.nc:
interface Leds {
/**
* Turn LED n on, off, or toggle its present state.
*/
async command void led0On();
async command void led0Off();
async command void led0Toggle();
async command void led1On();
async command void led1Off();
async command void led1Toggle();
async command void led2On();
async command void led2Off();
async command void led2Toggle();
/**
* Get/Set the current LED settings as a bitmask. Each bit corresponds to
* whether an LED is on; bit 0 is LED 0, bit 1 is LED 1, etc.
*/
async command uint8_t get();
async command void set(uint8_t val);
}
</PRE><PRE>tos/interfaces/Timer.nc:
interface Timer
{
// basic interface
command void startPeriodic( uint32_t dt );
command void startOneShot( uint32_t dt );
command void stop();
event void fired();
// extended interface omitted (all commands)
}
</PRE>
<P>Looking over the interfaces for <CODE>Boot</CODE>, <CODE>Leds</CODE>, and
<CODE>Timer</CODE>, we can see that since <CODE>BlinkC</CODE> uses those
interfaces it must implement handlers for the <CODE>Boot.booted()</CODE> event,
and the <CODE>Timer.fired()</CODE> event. The <CODE>Leds</CODE> interface
signature does not include any events, so <CODE>BlinkC</CODE> need not implement
any in order to call the Leds commands. Here, again, is <CODE>BlinkC</CODE>'s
implementation of <CODE>Boot.booted()</CODE>: </P><PRE>apps/Blink/BlinkC.nc:
event void Boot.booted()
{
call Timer0.startPeriodic( 250 );
call Timer1.startPeriodic( 500 );
call Timer2.startPeriodic( 1000 );
}
</PRE>
<P><CODE>BlinkC</CODE> uses 3 instances of the TimerMilliC component, wired to
the interfaces <CODE>Timer0</CODE>, <CODE>Timer1</CODE>, and
<CODE>Timer2</CODE>. The <CODE>Boot.booted()</CODE> event handler starts each
instance. The parameter to <CODE>startPeriodic()</CODE> specifies the period in
milliseconds after which the timer will fire (it's millseconds because of the
<TT><TMilli></TT> in the interface). Because the timer is started using
the <CODE>startPeriodic()</CODE> command, the timer will be reset after firing
such that the <CODE>fired()</CODE> event is triggered every n milliseconds. </P>
<P>Invoking an interface command requires the <TT>call</TT> keyword, and
invoking an interface event requires the <TT>signal</TT> keyword. BlinkC does
not provide any interfaces, so its code does not have any signal statements: in
a later lesson, we'll look at the boot sequence, which signals the Boot.booted()
event. </P>
<P>Next, look at the implementation of the <CODE>Timer.fired()</CODE>: </P><PRE>apps/Blink/BlinkC.nc:
event void Timer0.fired()
{
call Leds.led0Toggle();
}
event void Timer1.fired()
{
call Leds.led1Toggle();
}
event void Timer2.fired()
{
call Leds.led2Toggle();
}
}
</PRE>
<P>Because it uses three instances of the Timer interface, <CODE>BlinkC</CODE>
must implement three instances of <CODE>Timer.fired()</CODE> event. When
implementing or invoking an interface function, the function name is always
<I>interface</I>.<I>function</I>. As BlinkC's three Timer instances are named
<TT>Timer0</TT>, <TT>Timer1</TT>, and <TT>Timer2</TT>, it implements the three
functions <TT>Timer0.fired</TT>, <TT>Timer1.fired</TT>, and
<TT>Timer2.fired</TT>. </P><A name=TinyOS_Execution_Model:_Tasks></A>
<H1><SPAN class=mw-headline>TinyOS Execution Model: Tasks</SPAN></H1>
<P>All of the code we've looked at so far is <I>synchronous</I>. It runs in a
single execution context and does not have any kind of pre-emption. That is,
when synchronous (sync) code starts running, it does not relinquish the CPU to
other sync code until it completes. This simple mechanism allows the TinyOS
scheduler to minimize its RAM consumption and keeps sync code very simple.
However, it means that if one piece of sync code runs for a long time, it
prevents other sync code from running, which can adversely affect system
responsiveness. For example, a long-running piece of code can increase the time
it takes for a mote to respond to a packet. </P>
<P>So far, all of the examples we've looked at have been direct function calls.
System components, such as the boot sequence or timers, signal events to a
component, which takes some action (perhaps calling a command) and returns. In
most cases, this programming approach works well. Because sync code is
non-preemptive, however, this approach does not work well for large
computations. A component needs to be able to split a large computation into
smaller parts, which can be executed one at a time. Also, there are times when a
component needs to do something, but it's fine to do it a little later. Giving
TinyOS the ability to defer the computation until later can let it deal with
everything else that's waiting first. </P>
<P><B>Tasks</B> enable components to perform general-purpose "background"
processing in an application. A task is a function which a component tells
TinyOS to run later, rather than now. The closest analogies in traditonal
operating systems are <A class="external text"
title=http://www.tldp.org/LDP/tlk/kernel/kernel.html
href="http://www.tldp.org/LDP/tlk/kernel/kernel.html" rel=nofollow>interrupt
bottom halves</A> and <A class="external text"
title=http://opensource.adobe.com/twiki/bin/view/AdobeSource/DeferredProcSystem
href="http://opensource.adobe.com/twiki/bin/view/AdobeSource/DeferredProcSystem"
rel=nofollow>deferred procedure calls</A>. </P>
<P>Make a copy of the Blink application, and call it BlinkTask: </P><PRE>$ cd tinyos-2.x/apps
$ cp -R Blink BlinkTask
$ cd BlinkTask
</PRE>
<P>Open <CODE>BlinkC.nc</CODE>. Currently, the event handler for
<CODE>Timer0.fired()</CODE> is: </P><PRE>event void Timer0.fired() {
dbg("BlinkC", "Timer 0 fired @ %s\n", sim_time_string());
call Leds.led0Toggle();
}
</PRE>
<P>Let's change it so that it does a bit of work, enough that we'll be able to
see how long it runs. In terms of a mote, the rate at which we can see things
(about 24 Hz, or 40 ms) is <U>slow</U>: the micaZ and Telos can send about 20
packets in that time. So this example is really exaggerated, but it's also
simple enough that you can observe it with the naked eye. Change the handler to
be this: </P><PRE>event void Timer0.fired() {
uint32_t i;
dbg("BlinkC", "Timer 0 fired @ %s\n", sim_time_string());
for (i = 0; i < 400001; i++) {
call Leds.led0Toggle();
}
}
</PRE>
<P>This will cause the timer to toggle 400,001 times, rather than once. Because
the number is odd, it will have the end result of a single toggle, with a bit of
flickering in-between. Compile and install the program. You'll see that Led 0
introduces so much latency in the Led 1 and Led 2 toggles that you never see a
situation where only one is on. On TelosB motes, this long running task can
cause the Timer stack to completely skip events (try setting the count to
200,001 or 100,001). </P>
<P>The problem is that this computation is interfering with the timer's
operation. What we'd like to do is tell TinyOS to execute the computation later.
We can accomplish this with a <B>task</B>. </P>
<P>A task is declared in your implementation module using the syntax </P><PRE> task void taskname() { ... }
</PRE>
<P>where <TT>taskname()</TT> is whatever symbolic name you want to assign to the
task. Tasks must return <TT>void</TT> and may not take any arguments. To
dispatch a task for (later) execution, use the syntax </P><PRE> post taskname();
</PRE>
<P>A component can post a task in a command, an event, or a task. Because they
are the root of a call graph, a tasks can safely both call commands and signal
events. We will see later that, by convention, commands do not signal events to
avoid creating recursive loops across component boundaries (e.g., if command X
in component 1 signals event Y in component 2, which itself calls command X in
component 1). These loops would be hard for the programmer to detect (as they
depend on how the application is wired) and would lead to large stack usage.
</P>
<P>Modify BlinkC to perform the loop in a task: </P><PRE>task void computeTask() {
uint32_t i;
for (i = 0; i < 400001; i++) {}
}
event void Timer0.fired() {
call Leds.led0Toggle();
post computeTask();
}
</PRE>
<P>Telos platforms will still struggle, but mica platforms will operate OK. </P>
<P>The <TT>post</TT> operation places the task on an internal <B>task queue</B>
which is processed in FIFO order. When a task is executed, it runs to completion
before the next task is run. Therefore, and as the above examples showed, a task
should not run for long periods of time. Tasks do not preempt each other, but a
task can be preempted by a hardware interrupts (which we haven't seen yet). If
you need to run a series of long operations, you should dispatch a separate task
for each operation, rather than using one big task. The <TT>post</TT> operation
returns an <TT>error_t</TT>, whose value is either <TT>SUCCESS</TT> or
<TT>FAIL</TT>. A post fails if and only if the task is already pending to run
(it has been posted successfully and has not been invoked yet) <SUP
class=reference id=_ref-task_0><A title=""
href="http://docs.tinyos.net/index.php/Modules_and_the_TinyOS_Execution_Model#_note-task">[1]</A></SUP>.
</P>
<P>For example, try this: </P><PRE>uint32_t i;
task void computeTask() {
uint32_t start = i;
for (;i < start + 10000 && i < 400001; i++) {}
if (i >= 400000) {
i = 0;
}
else {
post computeTask();
}
}
</PRE>
<P>This code breaks the compute task up into many smaller tasks. Each invocation
of computeTask runs through 10,000 iterations of the loop. If it hasn't
completed all 400,001 iterations, it reposts itself. Compile this code and run
it; it will run fine on both Telos and mica-family motes. </P>
<P>Note that using a task in this way required including another variable
(<TT>i</TT>) in the component. Because computeTask() returns after 10,000
iterations, it needs somewhere to store its state for the next invocation. In
this situation, <TT>i</TT> is acting as a static function variable often does in
C. However, as nesC component state is completely private, using the
<TT>static</TT> keyword to limit naming scope is not as useful. This code, for
example, is equivalent: </P><PRE>task void computeTask() {
static uint32_t i;
uint32_t start = i;
for (;i < start + 10000 && i < 400001; i++) {}
if (i >= 400000) {
i = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -