Lesson 3:
Introducing Tasks for Application Data Processing
Last updated 15 August
2003 |
This lesson introduces the TinyOS notion of tasks, which can be used
to perform general-purpose "background" processing in an application. This
lesson makes use of the SenseTask application, which is a revision of
the Sense application from the previous lesson.
Task creation and
scheduling |
TinyOS provides a two-level scheduling hierarchy consisting of tasks
and hardware event handlers. Remember that the keyword async
declares a command or event that can be executed by a hardware event handler.
This means it could be executed at any time (preempting other code), so
async commands and events should do small amounts of work and complete
quickly. Additionally, you should pay attention to the possibility of data races
on all shared data accessed by an async command or event. Tasks
are used to perform longer processing operations, such as background data
processing, and can be preempted by hardware event handler.
A task is declared in your implementation module using the syntax
task void taskname() { ... }
where
taskname() is whatever
symbolic name you want to assign to the task. Tasks must return
void
and may not take any arguments.
To dispatch a task for (later) execution, use the syntax
post taskname();
A task can be posted from within a command, an
event, or even another task.
The post operation places the task on an internal task queue
which is processed in FIFO order. When a task is executed, it runs to completion
before the next task is run; therefore, a task should not spin or block for long
periods of time. Tasks do not preempt each other, but a task can be preempted by
a hardware event handler. 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 SenseTask
Application |
To illustrate tasks, we have modified the Sense application from Lesson 2,
which is found in apps/SenseTask.
The SenseTaskM component maintains a circular data buffer,
rdata, that contains recent photo sensor samples; the
putdata() function is used to insert a new sample into the buffer. The
dataReady() event simply deposits data into the buffer and posts a
task, called processData(), for processing.
SenseTaskM.nc // ADC data ready event handler async event result_t ADC.dataReady(uint16_t data) { putdata(data); post processData(); return SUCCESS; } |
Some time after the async event completes (there may be other tasks pending
for execution), the processData() task will run. It computes the sum of
the recent ADC samples and displays the upper three bits of the sum on the LEDs.
SenseTaskM.nc, continued task void processData() { int16_t i, sum=0;
atomic { for (i=0; i < size; i++) sum += (rdata[i] >> 7); } display(sum >> log2size); } |
The keyword atomic in the task processData() illustrates the
use of nesC atomic statements. This means the code section within the
atomic curly braces will execute without preemption. In this example,
access to the shared buffer rdata is being protected. Where else should
this be used ?
Atomic statements delay interrupt handling which makes the system less
responsive. To minimize this effect, atomic statements in nesC should avoid
calling commands or signaling events when possible (the execution time of an
external command or event will depend on what the component is wired to).
Try to break up the processData() task so that each invocation of
the task only adds one element of the rdata array to sum.
processData() should then post itself to continue processing the
complete sum, and display the LEDs when the final element of the array
has been processed. Be careful of concurrency issues - since
processData() is also posted from ADC.dataReady(), you might
want to add a flag to prevent a new instance of the task from being started
before the previous sum has been computed!
posted on 2005-11-28 01:05
井冈山 阅读(261)
评论(0) 编辑 收藏 引用 所属分类:
tinyOS