Download as pdf or txt
Download as pdf or txt
You are on page 1of 8

See discussions, stats, and author profiles for this publication at: https://www.researchgate.

net/publication/259527560

Build a Super Simple Tasker

Article · July 2006

CITATIONS READS

5 2,008

2 authors:

Miro Samek Robert Ward


Quantum Leaps Iowa State University
33 PUBLICATIONS 270 CITATIONS 8 PUBLICATIONS 9 CITATIONS

SEE PROFILE SEE PROFILE

Some of the authors of this publication are also working on these related projects:

A Data Flow Based Interpreter for System Dynamics simulation models View project

Theoretical and Financial models related to scrum. View project

All content following this page was uploaded by Miro Samek on 02 January 2014.

The user has requested enhancement of the downloaded file.


RTC KERNELS

Build a super simple tasker


By Miro Samek described in the trade press. We
Founder and CTO hope that this article provides a
Quantum Leaps, LLC convenient reference for those
interested in such a lightweight
Robert Ward scheduler. But more importantly,
Founder we hope to explain why a simple
The C Users Journal RTC kernel like SST is a perfect
Principal Software Engineer match for execution systems
Netopia Inc. built around state machines
including those based on ad-
Almost all embedded systems vanced UML statecharts. Be-
are event-driven; most of the cause state machines universally
time they wait for some event assume RTC execution semantics,
such as a time tick, a button it seems only natural that they
press, a mouse click, or the arrival should be coupled with a sched-
of a data packet. After recognis- uler that expects and exploits the
ing the event, the systems react RTC execution model.
by performing the appropriate We begin with a description
computation. This reaction might of how SST works and explain cated mechanisms to implement unlike most tasks in traditional
include manipulating the hard- why it needs only a single stack the context switch magic. The kernels.3 Instead, an SST task is
ware or generating secondary, for all tasks and interrupts. We kernel we’ll describe can be ultra a regular C-function that runs
“soft” events that trigger other then contrast this approach with simple because it doesn’t need to to completion and returns, thus
internal software components. the traditional real-time kernels, manage multiple stacks and all of removing itself from the execu-
Once the event-handling action which gives us an opportunity to their associated bookkeeping. tion stack as it completes. An SST
is complete, such reactive sys- re-examine some basic real-time By requiring that all tasks run task can be activated only by an
tems enter a dormant state in concepts. Next, we describe a to completion and enforcing ordinary C-function call from
anticipation of the next event.1 minimal SST implementation in fixed-priority scheduling, we the SST scheduler and always
Ironically, most real-time ker- portable ANSI C and back it up can instead manage all context returns to the scheduler upon
nels or RTOSes for embedded with an executable example that information using the machine’s completion. The SST sched-
systems force programmers to you can run on any x86-based natural stack protocol. Whenever uler itself is also an ordinary
model these simple, discrete PC. We conclude with refer- a task is preempted by a higher- C-function that’s called each
event reactions using tasks struc- ences to an industrial-strength priority task, the SST scheduler time a preemptive event occurs
tured as continuous endless single-stack kernel combined uses a regular C-function call to and that runs to completion and
loops. To us this seems a serious with an open-source state ma- build the higher-priority task con- returns once all tasks with priori-
mismatch--a disparity that’s re- chine-based framework, which text on top of the preempted-task ties higher than the preempted
sponsible for much of the famil- together provide a determin- context. Whenever an interrupt task have completed.
iar complexity of the traditional istic execution environment preempts a task, SST uses the al- The second consequence of
real-time kernels. for UML state machines. We’ll ready established interrupt stack the SST execution profile is that
In this article we’ll show how assume that you’re familiar with frame on top of which to build events associated with lower-
matching the discrete event basic real-time concepts, such the higher-priority task context, priority tasks must be queued
nature typical of most embed- as interrupt processing, context again using a regular C-function until the higher-priority tasks
ded systems with a simple run- switches, mutual exclusion and call. This simple form of context complete. The scheduling algo-
to-completion (RTC) kernel or blocking, event queues, and finite management is adequate be- rithm will work with a wide range
“tasker” can produce a cleaner, state machines. cause we’re insisting that every of prioritisation mechanisms;
smaller, faster, and more natural task, just like every interrupt, here we assume the simplest.
execution environment. In fact, Preemptive multi-tasking runs to completion. Because the For the sake of this discussion
we’ll show you how (if you model with a single stack preempting task must also run we’ll assume a priority number-
a task as a discrete, run-to-com- Conventional real-time kernels to completion, the lower-priority ing scheme in which SST tasks
pletion action) you can create maintain relatively complex ex- context will never be needed un- have priorities numbered from
a prioritised, fully preemptive, ecution contexts (including sep- til the preempting task (and any 1 to SST_MAX_PRIO, inclusive,
deterministic real-time kernel, arate stack spaces) for each run- higher-priority tasks that might and a higher number represents
which we call Super Simple Task- ning thread or task, as explained preempt it) has completed--at a higher urgency. We reserve the
er (SST), with only a few dozen in the excellent book MicroC/OS- which time the preempted task priority level 0 for the SST idle
lines of portable C code.2 II: The Real-Time Kernel by Jean will naturally be at the top of the loop, which is the only part of SST
Such a real-time kernel is not Labrosse.3 Keeping track of the stack, ready to be resumed. structured as an endless loop.
new; similar kernels are widely details of these contexts and The first consequence of this Simply calling a task function
used in the industry. Even so, switching among them requires execution profile is that an SST for every preemption level may
simple RTC schedulers are seldom lots of bookkeeping and sophisti- task cannot be an endless loop, seem too nave to work, but the

EE Times-India | July 2006 | eetindia.com 


task than itself (5), the SST sched- and finally executes the SST-spe-
uler will again be invoked (again cific exit (7). The exit code sends
a simple C call), but will return the end-of-interrupt (EOI) instruc-
immediately when it doesn’t find tion to the interrupt controller,
any ready tasks with a higher pri- restores the saved priority of the
ority than the current task. When interrupted task, and invokes
this second call to the scheduler the SST scheduler (8). Now, the
returns, the high-priority task runs scheduler detects that the high-
to completion (6) and naturally priority task is ready to run, so it
returns to the SST scheduler (7). enables interrupts and calls the
The SST scheduler checks once newly ready, high-priority task
more for a higher-priority task to (9). Note that the SST scheduler
start (8), but it finds none. The SST doesn’t return. The high-priority
analysis in the next section will A lower-priority task scheduler returns to the low-pri- task runs to completion (10) un-
show that it works perfectly for posts an event to a ority task, which continues (9). less it also gets interrupted. After
exactly the same reason that a higher-priority task: Obviously, synchronous pre- completion, the high-priority task
prioritised hardware-interrupt SST must immediately suspend emptions are not limited to only naturally returns to the scheduler
system works. the execution of the lower-pri- one level. If the high-priority (11). The scheduler checks for
ority task and start the higher- task were to post an event to a more higher-priority tasks to
Prioritisation of tasks priority task. We call this type still higher-priority task at point execute (12) but doesn’t find any
and interrupts in SST of preemption synchronous (5) of Figure 2, the high-prior- and returns. The original interrupt
It’s interesting to observe that preemption because it happens ity task would be synchronously returns to the low-priority task
most prioritised interrupt con- synchronously with posting an preempted and the scenario (13), which has been asynchro-
trollers (for example, the 8259A event to the task’s event queue. would recursively repeat itself at nously preempted all that time.
inside the x86-based PC, the AIC a higher level of priority. Note that the interrupt return
in AT91-based ARM MCUs from An interrupt posts an event Figure 3 illustrates the asyn- (13) matches the interrupt call (2).
Atmel, the VIC in LPC2xxx MCUs to a higher-priority task than chronous preemption scenario Finally, the low-priority task runs
from Philips, the interrupt con- the interrupted task: caused by an interrupt. Initially a to completion (14).
troller inside M16C from Renesas, Upon completion of the interrupt low-priority task is executing and It’s important to point out
and many others) implement in service routine (ISR), the SST must interrupts are unlocked (1). An that conceptually the interrupt
hardware the exact same asyn- start execution of the higher-pri- asynchronous event interrupts handling ends in the SST-specific
chronous scheduling policy for ority task instead of resuming the the processor (2). The processor interrupt exit (8), even though
interrupts as SST implements in lower-priority task. We call this immediately preempts any ex- the interrupt stack frame still
software for tasks. In particular, type of preemption asynchro- ecuting task and starts executing remains on the stack and the
any prioritised interrupt con- nous preemption because it can the ISR (interrupt service routine). IRET instruction has not yet ex-
troller allows only higher-prior- happen any time interrupts are The ISR executes the SST-specific ecuted. (The IRET instruction is
ity interrupts to preempt the not explicitly locked. entry (3), which saves the priority understood here generically to
currently active interrupt. All in- Figure 2 illustrates the syn- of the interrupted task on the mean a specific CPU instruction
terrupts must run to completion chronous preemption scenario stack and raises the current pri- that causes hardware interrupt
and cannot block. All interrupts caused by posting an event from ority to the ISR level. The ISR per- return.) The interrupt ends be-
nest on the same stack. a low-priority task to a high-prior- forms its work (4) which includes cause the EOI instruction is issued
In the SST execution model, ity task. Initially a low-priority task posting an event to the high- to the interrupt controller and the
tasks and interrupts are nearly is executing (1). At some point priority task (5). Posting an event interrupts get re-enabled inside
symmetrical: both tasks and during normal execution, the engages the SST scheduler, but the SST scheduler. Before the EOI
interrupt service routines are low-priority task posts an event the current priority (that of the instruction, the interrupt con-
one-shot, run-to-completion to a high-priority task, thus mak- ISR) is so high that the scheduler troller allows only interrupts of
functions. In fact, SST views in- ing the high-priority task ready returns immediately, not finding higher priority than the currently
terrupts very much like tasks of for execution. Posting the event any task with priority higher than serviced interrupt. After the EOI
“super high” priority, as shown in engages the SST scheduler (2). an interrupt. The ISR continues (6) instruction and subsequent call
Figure1, except that interrupts The scheduler detects that a
are prioritised in hardware (by the high-priority task has become
interrupt controller), while SST ready to run, so the scheduler
tasks are prioritised in software. calls (literally a simple C-function
call) the high-priority task (3).
Synchronous and Note that because the SST sched-
asynchronous preemptions uler launches the task on its own
As a fully preemptive kernel, SST thread, the scheduler doesn’t re-
must ensure that at all times the turn until after the higher-priority
CPU executes the highest-priority task completes. The high-priority
task that’s ready to run. Fortunately, task runs (4), but at some time it
only two scenarios can lead to too may post an event to other
readying a higher-priority task: tasks. If it posts to a lower-priority

 eetindia.com | July 2006 | EE Times-India


to the SST scheduler, interrupts construction and destruction of
get unlocked and the interrupt these stack frames must be pro-
controller allows all interrupt lev- grammed in assembly, because
els, which is exactly the behaviour a traditional kernel cannot leave
expected at the task level. the context switch magic to the C
Consequently, asynchronous compiler. In contrast, SST doesn’t
preemption is not limited to only really care about any particular
one level. The high-priority task stack frame or whether the
runs with interrupts unlocked, registers are stored immediately
shown as segment (10) in Figure 3, upon the ISR entry or stepwise,
so it too can be asynchronously as needed. The only relevant
preempted by an interrupt, in- aspect is that the CPU state be
cluding the same-level interrupt restored exactly to the status
that launched the task at point before the interrupt, but it’s
(9). If the interrupt posts an irrelevant how this happens.
event to a still higher-prior- This means that the compiler-
ity task, the high-priority task generated interrupts that most more RAM for each private stack Figure 4 shows the per-task
will also be asynchronously embedded C compilers support and more CPU cycles (perhaps by data structures maintained by
preempted and the scenario are typically adequate for SST, factor of two) than SST. a traditional preemptive kernel
will recursively repeat itself at a but are often inadequate for tra- On a side note, we would like where each task has its own
higher-level of priority. ditional kernels. Not only does to emphasise that all traditional stack and a task control block
SST not require any assembly blocking kernels in the indus- (TCB)3 for the execution con-
SST versus traditional programming, but in this case try share the problems we’ve text of each task. In general,
kernels the compiler-generated inter- described. We refer here to an interrupt handler stores the
By managing all contexts in a rupt entry and exit code is often Labrosse’s book MicroC/OS-II interrupt context on one task’s
single stack, SST can run with superior to custom assembly, not because MicroC/OS-II has stack and restores the context,
significantly less RAM than a because the C compiler is in a any special deficiency in this from another task’s stack. After
typical blocking kernel. Because much better position to globally regard but because his book is restoring a task’s context into
tasks don’t have separate stacks, optimise interrupt stack frames an excellent treatment of tra- the CPU registers, the tradi-
no unused private stack space is for specific ISRs. In this respect, ditional kernels and is familiar tional scheduler always issues
associated with suspended tasks. SST allows you to take ad- to many embedded systems the IRET instruction. The key
SST task switches also tend to vantage of the C compiler’s programmers.3 point is that the interrupt
incur less execution overhead; a capabilities, something a context remains saved on the
traditional kernel doesn’t distin- traditional kernel can’t do. Interrupt duration in p r e e mpt e d t a s k ’ s s t a c k , s o
guish between the synchronous The last point is perhaps best traditional kernels and SST the saved interrupt context
and asynchronous preemptions illustrated by an example. All C If you have some experience with outlives the duration of the
and charges you a uniformly high compilers for ARM processors, traditional preemptive kernels, interrupt handler. Therefore
price for all context switches. for instance, adhere to the ARM you’ll notice that SST seems to defining the duration of an
Finally, SST uses much simpler Procedure Call Standard (APCS) encourage programmers to vio- interrupt from saving the in-
and smaller task control blocks that prescribes which registers late time-honoured advice about terrupt context to restoring the
(TCBs) for each task. must be preserved across a C- what to put in an ISR. context is problematic.
Because of this simplicity, con- function call and which can be Most of us have been taught The situation is not really that
text switches in SST (especially clobbered. The C-compiler-gen- that an ISR should be as short much different under a single-
the synchronous preemptions) erated ISR entry saves initially as possible and that the main stack kernel, such as SST. An ISR
can involve much less stack space only the registers that might work should always be done at stores the interrupt context on
and CPU overhead than under be clobbered in a C function, the task level. In SST, however, the stack, which happens to
any traditional kernel. But even which is only about half of all everything appears to be done be common for all tasks and
the asynchronous preemptions in ARM registers. The rest of the at the ISR context and nothing interrupts. After some process-
SST end up typically using signifi- registers get saved later, inside at the task context. It seems to ing, the ISR calls the sched-
cantly less stack space and fewer C functions invoked from the be backwards. uler, which internally unlocks
CPU cycles. Here’s why. ISR, if and only if such registers But really, the problem cen- interrupts. If no higher-prior-
Upon an interrupt, a tradi- are actually used. This example tres on the distinction between ity tasks are ready to run, the
tional kernel must establish a of a context save occurring in an interrupt and a task. SST scheduler exits immediately,
strictly defined stack frame on several steps is perfectly suited forces us to revise the nave in which case the ISR restores
each private stack into which it to the SST. In contrast, a tradi- understanding of interrupt the context from the stack and
saves all CPU registers. Unlike SST, tional kernel must save all ARM duration as beginning with sav- returns to the original task ex-
which can exploit the compiler’s registers in one swoop upon ing interrupt context on a stack actly at the point of preemption.
natural register-management ISR entry, and if the assembly and ending with restoring the Otherwise, the SST scheduler
strategy, a traditional kernel ISR “wrapper” calls C functions interrupt context followed by calls a higher-priority task and
must be prepared to restore (which it typically does) many IRET because, as it turns out, this the interrupt context remains
all registers when it resumes registers are saved again. Need- definition is problematic even saved on the stack, just as in the
the preempted task. Often the less to say, such policy requires for traditional kernels. traditional kernel.

EE Times-India | July 2006 | eetindia.com 


The point here is that the ISR is The example The SST example calls with 3-digit precision. The contains the Turbo C++ project
defined from the time of storing demonstrates the multi-tasking last “Preemptions” column shows file to build and debug the ap-
interrupt context to the time of and preemption capabilities the number of asynchronous pre- plication. (NOTE: because the
sending the EOI (End Of Interrupt) of SST. This example, shown in emptions of a given task or ISR. standard keyboard ISR is replaced
command to the interrupt con- Figure 5, consists of three tasks The SST example application by the custom one, debugging
troller followed by invoking the and two ISRs. The clock tick ISR intentionally uses two indepen- this application with the Turbo
scheduler that internally enables produces a “tick event” every 5ms, dent interrupts (the clock tick and C++ IDE might be difficult.)
interrupts, not necessarily to the while the keyboard ISR produces keyboard) to create asynchronous
point of restoring the interrupt a “key event” every time a key is preemptions. To further increase Critical sections in SST
context. This definition is more SST, just like any other kernel,
precise and universal, because needs to perform certain opera-
under any kernel the interrupt tions indivisibly. The simplest and
context remains stored on one most efficient way to protect a
stack or another and typically out- section of code from disruptions
lives the duration of an interrupt’s is to lock interrupts on entry to
processing. Of course, you should the section and unlock the inter-
strive to keep the ISR code to a rupts again on exit. Such a section
minimum, but in SST, you should of code is called a critical section.
remember that the final call to Processors generally provide
the SST scheduler is conceptually instruction s t o l o c k / u n l o c k
not a part of the ISR, but rather it interrupts, and your C com-
constitutes the task level. piler must have a mechanism
The definition of ISR duration to perform these operations
is not purely academic, but has from C. Some compilers allow
important practical implications. you to include inline assembly
In particular, debugging at the instructions in your C source.
ISR level can be much more chal- Other compilers provide lan-
lenging than debugging at the task guage extensions or at least C-
level. Indeed, in SST debugging the callable functions to lock and
ISR to the point of sending the EOI unlock interrupts from C.
command to the interrupt control- To hide the actual imple-
ler and invoking the SST scheduler mentation method chosen, SST
is as hard as in any traditional ker- provides two macros to lock and
nel, especially when debugging unlock interrupts. Here are the
is accomplished through a ROM macros defined for the Turbo
monitor. However, debugging an depressed or released, and at the the probability of an interrupt C++ compiler:
SST task is as easy as debugging auto-repeat rate of the keyboard preempting a task, and of an in- reformat this as:
the main() task, even though a when a key is depressed and held. terrupt preempting an interrupt, #define SST_INT_LOCK() \
task is called indirectly from the ISR. The two “tick tasks”: tickTaskA() the code is peppered with calls disable()
This is because the SST scheduler and tickTaskB() receive the “tick to a busyDelay() function, which #define SST_INT_UNLOCK() \
launches each task with interrupts events” and place a letter A or extends the run-to-completion enable()
unlocked at the CPU level and B, respectively, at a random lo- time in a simple counted loop.
with the interrupt controller set to cation in the right-hand panel You can specify the number of In the minimal SST version,
enable all interrupts. of the screen. The keyboard iterations through this loop by we assume the simplest possible
task kbdTask(), with the prior- a command line parameter to critical section: one that uncon-
SST implementation ity between tickTaskA() and the application. You should be ditionally locks interrupts upon
We first present a minimal stand- tickTaskB(), receives the scan careful not to go overboard with entry and unconditionally un-
alone implementation of SST. The codes from the keyboard and this parameter, though, because locks interrupts upon exit. Such
presented code is portable ANSI sends “colour events” to the larger values will produce a task simple critical sections should
C, with all CPU and compiler-spe- tick tasks, which change the set that is not schedulable and never nest, because interrupts
cific parts clearly separated out. colour of the displayed letters the system will (properly) start will always be unlocked upon exit
However, the implementation in response. Also, the kbdTask() losing events. from the critical section, regard-
omits some features that might terminates the application when The following subsections less of whether they were locked
be needed in real-life applications. you depress the Esc key. explain the SST code and the or unlocked before the entry.
The main goal at this point is to The left-hand side of the application structure. The com- The SST scheduler is designed
clearly demonstrate the key con- screen in Figure 5 shows the plete SST source code consists to never nest critical sections,
cepts while letting you execute basic statistics of the running of the header file sst.h located but you should be careful when
the code on any Windows-based application. The first two columns in the include\ directory and the using macros SST_INT_LOCK()
PC. We’ve compiled the example display the task names and pri- implementation file sst.c located and SST_INT_UNLOCK() to pro-
with the legacy Turbo C++ 1.01, orities. Note that there are many in the source\ directory. The tect your own critical sections in
available as a free download from unused priority levels. The “Calls” example files are located in the the applications. You can avoid
the Borland Museum.4 column shows the number of example\ directory, which also this limitation by using smarter

 eetindia.com | July 2006 | EE Times-India


(though somewhat more expen- every ISR is entered with SST interrupt-entry and inter- writes the EOI to the master
sive) code to lock and unlock interrupts locked */ rupt-exit macros at the begin- 8259A PIC) (3) Restores the ini-
interrupts. Please note, however, uint8_t pin; /* temporary ning and end of each ISR. (If tial SST priority (4) Calls the SST
that the inability to nest critical variable to store the initial the interrupt source requires scheduler, which performs the
sections does not necessarily priority */ clearing, this should be done “asynchronous preemption,” if
mean that you can’t nest inter- SST_ISR_ENTRY(pin, TICK_ before calling SST_ISR_ENTRY()). necessary The task control blocks
rupts. On processors equipped ISR_PRIO); The macros SST_ISR_ENTRY() Like other real-time kernels, SST
with an internal or external inter- and SST_ISR_EXIT() are defined keeps track of tasks in an array of
rupt controller, such as the 8259A SST_post(TICK_TASK_A_ in the includes\sst.h header file data structures called task control
PIC in the x86-based PC, or the AIC PRIO, TICK_SIG, 0); /* post as shown in Listing 2. (The do.. blocks (TCBs). Each TCB contains
in the AT91 ARM microcontroller, the Tick to Task A */ while(0) loop around the macros such information as the pointer
you can unlock the interrupts SST_post(TICK_TASK_B_ is only for syntactically correct to the task function, the task mask
inside ISRs at the processor level, PRIO, TICK_SIG, 0); /* post grouping of the instructions.) (calculated as (1 << (priority - 1))
thus avoiding nesting of the critical the Tick to Task B */ ), and the event queue associated
section inside ISRs, and let the inter- Listing 2: Definition of the with the task. The TCB takes only
rupt controller handle the interrupt SST_ISR_EXIT(pin, SST interrupt entry/exit 8 to 10 bytes, depending on the
prioritisation and nesting before outportb(0x20, 0x20)); macros size of the function pointer. Ad-
they even reach the CPU core. } #define SST_ISR_ENTRY(pin_, ditionally, you need to provide an
/*............................................................ isrPrio_) do { \ event queue buffer of the correct
Interrupt processing in SST ..............*/ (pin_) = SST_currPrio_; \ size when you create a task. The
One of the biggest advantages void interrupt kbdISR() { /* SST_currPrio_ = (isrPrio_); \ TCB used here is optimised for
of SST is the simple interrupt every ISR is entered with SST_INT_UNLOCK(); \ a small, fixed number of priority
processing, which is actually not interrupts locked */ } while (0) levels and simple events--re-
much more complicated with uint8_t pin; /* temporary strictions that make sense in a
SST than it is in a simple “super- variable to store the initial #define SST_ISR_EXIT(pin_, classic embedded environment.
loop” (a.k.a., main+ISRs). Because priority */ EOI_command_) do { \ Neither of these limitations,
SST doesn’t rely in any way on the uint8_t key = inport(0x60);/ SST_INT_LOCK(); \ however, is required by the SST
interrupt stack frame layout, with *get scan code from the (EOI_command_); \ scheduling algorithm.
most embedded C compilers, the 8042 kbd controller */ SST_currPrio_ = (pin_); \
ISRs can be written entirely in C. SST_ISR_ENTRY(pin, KBD_ SST_schedule_(); \ Posting events to tasks
One notable difference between ISR_PRIO); } while (0) In this minimal version of SST,
a simple “super-loop” and SST events are represented as struc-
ISRs is that SST requires the pro- SST_post(KBD_TASK_PRIO, The SST_ISR_ENTRY() macro tures containing two byte-size
grammer to insert some simple KBD_SIG, key); /* post the is invoked with interrupts locked elements: an event type identi-
actions at each ISR entry and exit. Key to the KbdTask */ and performs the following three fier (for example, the key-press
These actions are implemented steps: (1) Saves the initial SST pri- occurrence), and the parameter
in SST macros SST_ISR_ENTRY() SST_ISR_EXIT(pin, ority into the stack variable ‘pin_’ associated with the occurrence
and SST_ISR_EXIT(). The code outportb(0x20, 0x20)); (2) Sets the current SST priority (for example, the scan code of
snippet in Listing 1 shows how } to the ISR level (3) Unlocks the the key). The events are stored in
these macros are used in the interrupts to allow interrupt nest- event queues organised as stan-
clock tick and keyboard ISRs Note in Listing 1, the com- ing The SST_ISR_EXIT() macro is dard ring buffers. SST maintains
from the example application piler specific keyword “interrupt”, invoked with interrupts unlocked the status of all event queues
defined in the file example\bsp.c. which directs the Turbo-C com- and performs the following four in the variable called the SST
piler to synthesise appropriate steps: (1) Locks the interrupts ready-set. As shown in Figure
Listing 1: SST ISRs from the context saving, restoring, and (2) Writes the EOI command 6, the SST ready-set SST_ready-
example application interrupt return prologues and to the interrupt controller (for Set_ is just a byte, meaning that
void interrupt tickISR() { /* epilogues. Please also note the example, outportb(0x20, 0x20) this implementation is limited
to eight priority levels. Each bit
in the SST_readySet_ represents
one SST task priority. The bit
number ‘n’ in the SST_readySet_
is 1 if the event queue of the task
of priority ‘n+1’ is not empty (bits
are numbered 0..7). Conversely,
bit number ‘m’ in SST_readySet_
is 0 if the event queue of the task
of priority ‘m+1’ is empty, or the
priority level ‘m+1’ is not used.
Listing 3 shows the event
posting function SST_post(),
which uses a standard ring buf-
fer algorithm (FIFO). If the event

EE Times-India | July 2006 | eetindia.com 


is inserted to an empty queue variables SST_currPrio_ and SST_ this becomes the current task ideal situation allows you to
(1), the corresponding bit in readySet_ are always accessed in priority */ program all SST tasks with purely
SST_readySet_ is set (2), and a critical section to prevent data (6) SST_INT_UNLOCK(); /* sequential techniques, allowing
the SST scheduler is invoked corruption. (See also the SST_ISR_ unlock the interrupts */ SST to handle all the details of
to check for the “synchronous ENTRY/SST_ISR_EXIT macros.) (7) (*tcb->task__)(e); /* thread-safe event exchange and
preemption” (3). Listing 4 shows the complete SST call the SST task */ queuing. However, at the cost of
scheduler implementation. (Yes, (8) SST_INT_LOCK(); /* increased coupling among tasks,
Listing 3: Event it really is that small.) The function lock the interrupts for the next you might choose to share select-
posting in SST SST_schedule_() must be called pass */ ed resources. If you go this path,
uint8_t SST_post(uint8_t prio, with interrupts locked and re- } you must accept responsibility
SSTSignal sig, SSTParam par) { turns also with interrupts locked. (9) SST_currPrio_ = pin; for synchronising access to such
TaskCB *tcb = &l_taskCB[prio /* restore the initial priority */ resources (shared variables or de-
- 1]; Listing 4: The SST scheduler } vices). One option is to guard ac-
SST_INT_LOCK(); void SST_schedule_(void) { cess to the shared resource with
if (tcb->nUsed__ < tcb- static uint8_t const log2Lkup[] Just as the hardware-interrupt a critical section. This requires the
>end__) { ={ /* log-base-2 lookup controller does when servicing an programmer to consistently lock
tcb->queue__[tcb->head_ table */ interrupt, the SST scheduler starts and unlock interrupts around
_].sig = sig; /* insert the 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, by saving the initial priority into a each access. For very short ac-
event at the head */ 4, 4, 4, 4, 4, stack variable ‘pin’ (1). Next, this cesses this might well be the
tcb->queue__[tcb->head_ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, initial priority is compared to the most effective synchronisation
_].par = par; 5, 5, 5, 5, 5, highest priority of all tasks ready mechanism. However, SST also
if ((++tcb->head__) == 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, to run (computed from the SST provides a more selective mech-
tcb->end__) { 6, 6, 6, 6, 6, ready-set SST_readySet_. ) This anism: a priority-ceiling mutex.
tcb->head__ = (uint8_ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, latter computation is efficiently Priority-ceiling mutexes are im-
t)0; /* wrap the head */ 6, 6, 6, 6, 6, implemented as a binary-logarithm mune to priority inversions,5 but
} 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, lookup (more precisely log2(x) + still allow hardware interrupts
7, 7, 7, 7, 7, 1), which delivers the bit number and higher-priority tasks to run as
(1) if ((++tcb->nUsed__) == 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, of the most-significant 1-bit in usual. The SST implementation of
(uint8_t)1) { /* the first 7, 7, 7, 7, 7, the SST_readySet_ byte (2). If the a priority-ceiling mutex is remark-
event? */ ... new priority ‘p’ is higher than the ably simple. Recall that the SST
(2) SST_readySet_ |= tcb- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, initial value, the scheduler needs scheduler can only launch tasks
>mask__; /* insert task to 8, 8, 8, 8, 8, to start the task of priority ‘p’. First, with priorities higher than the ini-
the ready set */ ... the scheduler removes the event tial priority with which the sched-
(3) SST_schedule_(); /* }; from the tail of the queue (3), uler was entered. This means that
check for synchronous pre- and if the queue becomes empty temporarily increasing the current
emption */ (1) uint8_t pin = SST_currPrio_; clears the corresponding bit in SST priority SST_currPrio_ blocks
} /* save the initial priority */ SST_readySet_ (4). Subsequently, any tasks with priorities lower
SST_INT_UNLOCK(); uint8_t p; /* the current priority SST_currPrio_ than this priority “ceiling.” This is
return (uint8_t)1; the new priority */ is raised to the new level ‘p’ (5), the exactly what a priority-ceiling
/* event successfully posted /* is the interrupts are unlocked (6) and the mutex is supposed to do. Listing
*/ new priority higher than the high-priority task is called (7). When 5 shows the SST implementation.
} initial? */ the task completes and returns,
else { (2) while ((p = log2Lkup[SST_ the interrupts are locked (8), and Listing 5: Priority-ceiling
SST_INT_UNLOCK(); readySet_]) > pin) { the while loop again computes mutex locking and unlocking
return (uint8_t)0; TaskCB *tcb = &l_taskCB[p the highest-priority task ready uint8_t SST_
/* queue full, event posting - 1]; to run based on the potentially mutexLock(uint8_t prioCeil-
failed */ /* get changed SST_readySet_ (2). The ing) {
} the event out of the queue */ loop continues until no more uint8_t p;
} (3) SSTEvent e = tcb->queue__ tasks above the initial priority SST_INT_LOCK();
[tcb->tail__]; level ‘pin’ are ready to run. Before p = SST_currPrio_; /*
The SST scheduler if ((++tcb->tail__) == tcb- the exit, the scheduler restores save the original SST priority
The SST scheduler is a simple C- >end__) { the current priority SST_currPrio_ to return */
function SST_schedule_() whose tcb->tail__ = (uint8_t)0; from the stack variable ‘pin’ (9). if (prioCeiling > SST_cur-
job is to efficiently find the high- } rPrio_) {
est-priority task that is ready to run if ((—tcb->nUsed__) == Mutual exclusion in SST SST_currPrio_ = prioCeil-
and run it, if its priority is higher (uint8_t)0) { /* is the queue SST is a preemptive kernel, and ing; /* set the SST priority to
than the currently serviced SST becoming empty?*/ as with all such kernels, you must the ceiling */
priority. To perform this job, the (4) SST_readySet_ &= ~tcb- be very careful with any resource }
SST scheduler uses the already >mask__; /* remove from sharing among SST tasks. Ideally, SST_INT_UNLOCK();
described SST_readySet_ and the ready set */ SST tasks should not share any return p;
the current priority level SST_cur- } resources, limiting all intertask }
rPrio_ shown in Figure 6. Both (5) SST_currPrio_ = p; /* communication to events. This /*............................................................

 eetindia.com | July 2006 | EE Times-India


..............*/ Starting multi-tasking and Where to go from here echarts. Simply put, SST and state
void SST_ the SST idle loop The minimal SST implementa- machines are born for each other.
mutexUnlock(uint8_t org- Listing 7 shows the SST_run() tion presented in this article We would even go so far as to
Prio) { function, which is invoked from is remarkably powerful, given suggest that if you’re using any
SST_INT_LOCK(); main() when the application that it takes only 421 bytes of other type of real-time kernel
if (orgPrio < SST_currPrio_) transfers control to SST to start code (see Figure 5), 256 bytes for executing concurrent state
{ multi-tasking. of lookup tables, and several machines, you are probably
SST_currPrio_ = orgPrio; bytes of RAM per task (for the paying too much in ROM, RAM,
/* restore the saved priority Listing 7: Starting SST TCB and the event queue buf- and CPU usage.
to unlock */ multi-tasking fer) plus, of course, the stack. While the full integration of
SST_schedule_(); /* the void SST_run(void) { This might be all you need SST into a generalised, state
scheduler unlocks the inter- for smaller projects. When machine-based system is be-
rupts internally */ (1) SST_start(); you tackle bigger systems, yond the scope of this article,
} /* start ISRs */ however, you will inevitably you can explore the fit your-
SST_INT_UNLOCK(); (2) SST_INT_LOCK(); discover shortcomings in this self in an open-source sys-
} (3) SST_currPrio_ = (uint8_t)0; /* implementation. From our tem produced by one of the
set the priority for the SST idle experience, these limitations authors. You can download
Unlike the simple INT_LOCK loop */ have little to do with the complete code, examples, and
macros, the SST mutex interface (4) SST_schedule_(); /* multi-tasking model and much documentation from www.
allows locks to be nested, be- process all events produced to do with the infrastructure quantum-leaps.com. For the
cause the original SST priority is so far */ surrounding the kernel, such code and other supporting
preserved on the stack. Listing (5) SST_INT_UNLOCK(); as the limited event parameter documentation about the
6 shows how the mutex is used (6) for (;;) { /* size, lack of error handling, implementation in this article,
in the SST example to protect the SST idle loop */ and the rather primitive event- go to Embedded.com’s code
the nonre-entrant DOS random (7) SST_onIdle(); /* passing mechanism. archive.
number generator calls inside invoke the on-idle callback One thing that you’ll surely
the clock-tick tasks tickTaskA() */ discover when you go beyond Endnotes:
and tickTaskB(). } toy applications suitable for pub- 1. Selic, Bran. “The Challenges of
} lication in an article is that your Real-Time Software Design,”
Listing 6: Priority-ceiling mu- task functions will grow to con- Embedded Systems Program-
tex used in the SST example The SST_run() function calls tain more and more conditional ming, October 1996. (Article
to protect the nonre-entrant the SST_start() callback (1), in code to handle various modes of not available online.)
random-number generator which the application can con- operation. For example, a user- 2. Ward, Robert. “Practical Real-
void tickTaskA(SSTEvent e) { figure and start interrupts (see file interface task for a digital camera Time Techniques,” Proceed-
... examples\bsp.c). After locking in- must react differently to button- ings of the Embedded Sys-
uint8_t x, y; terrupts (2), the current SST priority press events when the camera tems Conference, San Fran-
uint8_t mutex; /* SST_currPrio_ is set to zero, which is in the playback mode than to cisco, 2003. Article pdf
mutex object preserved on corresponds to the priority of the the same button-press events 3. Labrosse, Jean J. MicroC/OS-
the stack */ idle loop. (The SST priority is stati- in the picture-taking mode. The II: The Real Time Kernel, 2nd
cally initialised to 0xFF.) After lower- best-known method of structur- Edition. CMP Books 2002.
mutex = SST_ ing the priority the SST scheduler is ing such code is through a state 4. Borland Developer Network,
schedLock(TICK_TASK_B_ invoked (4) to process any events machine that explicitly captures “Antique Software: Turbo
PRIO); /* mutex lock */ that might have accumulated the different modes of operations C++ version 1.01” http://
x = random(34); /* call to during the task initialisation phase. as different states of the system. bdn.borland.com/arti-
non-reentrant random num- Finally, interrupts are unlocked (5), And here is the most im- cle/0,1410,21751,00.html
ber generator */ and SST_run() enters the SST idle portant significance of SST. The 5. Kalinsky, David. “Mutexes
y = random(13); /* call to loop (6), which runs when the CPU inherent run-to-completion (RTC) Prevent Priority Inversions,”
non-reentrant random num- is not processing any of the one- execution model responsible for Embedded Systems Program-
ber generator */ shot SST tasks or interrupts. The the simplicity of this kernel per- ming, August 1998. (Article
SST_schedUnlock(mutex); idle loop continuously calls the fectly matches the RTC execution not available online.)
/* mutex unlock */ SST_onIdle() callback (7), which semantics universally assumed
... the application can use to put the in all state machine formalisms,
} CPU into a power-saving mode. including advanced UML stat- Email   Send inquiry

EE Times-India | July 2006 | eetindia.com 

View publication stats

You might also like