weAutSys uses Protothreads as its multi-threading base for all automation / user cycles as well as for system, alarm and I/O handling threads. Adam Dunkels' invention is very lightweight in terms of RAM and processor time consumptions and thus best fit for ยต-controller based automation systems.
In weAutSys Protothreads is used with only minimal modifications / additions to Adam Dunkels' ingenious original.
In (mostly) Adam Dunkels' words (3 June 2006):
Protothreads are extremely lightweight stackless threads designed for severely memory constrained systems such as small embedded systems or sensor network nodes. Protothreads can be used with or without an underlying operating system.
Protothreads provides a blocking context on top of an event-driven system, without the overhead of per-thread stacks. The purpose of Protothreads is to implement sequential flow of control without complex state machines or full multi-threading.
Main features:
The Protothreads library is released under an open source BSD-style license that allows for both non-commercial and commercial usage. The only requirement is that credit is given.
The Protothreads library was written by Adam Dunkels adam@ with support from Oliver Schmidt sics .seol.sc. @web .de
Modules | |
Local continuations | |
Files | |
file | pt.h |
Protothreads implementation. | |
Data Structures | |
struct | pt |
A protothread's (raw) data structure. More... | |
Defines | |
#define | PT_ENDED |
thread has finished | |
#define | PT_EXITED |
thread has finished | |
#define | PT_WAITING 0 |
thread paused due to a condition | |
#define | PT_YIELDED |
Thread paused. | |
#define | ptfnct_t |
The return type of a protothread function. | |
Initialization | |
#define | PT_INIT(pt) |
Initialise a protothread. | |
Declaration and definition | |
#define | PT_BEGIN(pt) |
Declare the start of a protothread inside the protothread function. | |
#define | PT_END(pt) |
Declare the end of a protothread. | |
Blocked wait | |
#define | PT_WAIT_UNTIL(pt, condition) |
Block and wait until condition is true. | |
#define | PT_WAIT_WHILE(pt, cond) |
Block and wait while condition is true. | |
Hierarchical protothreads | |
#define | PT_WAIT_THREAD(pt, thread) |
Block and wait until a child protothread completes. | |
#define | PT_SPAWN(pt, child, thread) |
Spawn a child protothread and wait until it exits. | |
Exiting and restarting | |
#define | PT_RESTART(pt) |
Restart the protothread. | |
#define | PT_EXIT(pt) |
Exit the protothread. | |
#define | PT_LEAVE(pt) |
End the protothread. | |
Yielding from a protothread | |
#define | PT_YIELD(pt) |
Yield from the current protothread. | |
#define | PT_YIELD_UNTIL(pt, cond) |
Yield from the protothread until a condition occurs. | |
#define | PT_YIELD_WHILE(pt, condition) |
Yield while a condition is true. | |
#define | PT_WAIT_ASYIELD_WHILE(pt, condition) |
Wait while a condition is true mimicked as yield. | |
#define | PT_OR_YIELD_HERE(pt) |
Yield only if a previous conditional wait did not. | |
#define | PT_OR_YIELD_REENTER() |
Yield only if a previous conditional wait did not and re-enter that. | |
Typedefs | |
typedef struct pt | pt_t |
A protothread's (raw) data structure as type. |
#define ptfnct_t |
The return type of a protothread function.
/note Within the PT_BEGIN PT_END braces a protothread function must not return directly (forgetting rare exceptions with lc-addrlabels). The return is done by Protothread operations.
Return values are PT_WAITING, PT_YIELDED, PT_EXITED and PT_ENDED.
The following holds:
a) PT_WAITING < PT_YIELDED < PT_EXITED < PT_ENDED
b) There is no real semantic difference between PT_EXITED and PT_ENDED
The knowledge a) is useful in evaluating return values.
The difference of PT_EXITED and PT_ENDED is almost insignificant:
The macro PT_END which returns PT_ENDED must appear exactly once at the end of a protothread function because it is the closing bracket to PT_BEGIN.
The macro PT_EXIT returning PT_EXITED may on the other hand appear as often as useful.
The effect of PT_EXIT and PT_END is exactly the same:
The thread does end meaning its next schedule will "land" / start thread work after PT_BEGIN. (Hence you will hardly find any code that won't OR PT_EXITED and PT_ENDED in concerning conditionals.
An extra macro PT_LEAVE returns PT_ENDED and can be used anywhere (saving jumps to PT_END just for the intended return value).
#define PT_YIELDED |
Thread paused.
Yielding is mostly done as courtesy to other threads or to obey CPU usage trime limits. As Protothreads are non-preemptive this is the responsibility of every single thread function!
Insofar the semantic difference to waiting is the (extra) reason to pause the thread, lest it runs too long. In that sense yielding with an condition should yield at least once no matter the condition's state — as does the original Protothread.
On the other hand weAutSys needed a to convey the (semantic) difference on the reason for a wait on a condition:
Herefore we use PT_YIELDED vs. PT_WAITING to transport this information via the (child) thread functions return value.
Initialise a protothread.
Initialisation must be done prior to starting to execute the protothread.
pt | A pointer to the protothread control structure. |
Declare the start of a protothread inside the protothread function.
This macro is used to declare the starting point of a protothread. It should be placed at or near the start of the function in which the protothread runs respectively is implemented. PT_BEGIN() is the gridiron to the last set (left, yield, block) re-entry point.
All statements above the PT_BEGIN() invocation will be executed each time the protothread is scheduled.
The statements below PT_BEGIN() will be executed at the first entry into an initialised or restarted protothread.
pt | the pointer to the protothread control structure. |
Declare the end of a protothread.
This macro is used for declaring that a protothread ends. It must always be used together with a matching PT_BEGIN() macro. A statement below won't be reached (except when labeled and gone to).
pt | the pointer to the protothread control structure. |
#define PT_WAIT_UNTIL | ( | pt, | |
condition | |||
) |
Block and wait until condition is true.
This macro blocks the protothread until the specified condition is true.
pt | pointer to the protothread control structure |
condition | the condition (a boolean expression) |
#define PT_WAIT_WHILE | ( | pt, | |
cond | |||
) |
Block and wait while condition is true.
This function blocks and waits while condition is true. See PT_WAIT_UNTIL().
pt | pointer to the protothread control structure |
cond | the condition (a boolean expression) |
#define PT_WAIT_THREAD | ( | pt, | |
thread | |||
) |
Block and wait until a child protothread completes.
This macro schedules a child protothread. The current protothread will block until the child protothread completes.
pt | A pointer to the (calling) protothread control structure. |
thread | The child protothread with arguments |
Spawn a child protothread and wait until it exits.
This macro spawns a child protothread and waits until it exits. The macro can only be used within a protothread.
pt | A pointer to the (calling) protothread control structure |
child | A pointer to the child protothread's control structure |
thread | The child protothread's function call with arguments |
#define PT_RESTART | ( | pt | ) |
Restart the protothread.
This macro will block once and cause the running protothread to restart its execution at the place after PT_BEGIN().
pt | A pointer to the protothread control structure |
Exit the protothread.
This macro causes the protothread to exit. If the protothread was spawned by another protothread, the parent protothread will become unblocked and can continue to run.
pt | A pointer to the (raw) protothread control structure |
Yield from the current protothread.
This function will yield the protothread, thereby allowing other processing to take place. As Protothreads are non-preemptive by nature unconditional yielding is one (and the only) way to dependably subdivide longer running tasks into junks of maximum allowed execution time.
pt | A pointer to the protothread control structure |
#define PT_YIELD_UNTIL | ( | pt, | |
cond | |||
) |
Yield from the protothread until a condition occurs.
This function will yield the protothread, until the specified condition evaluates to true.
The thread yields at least once, even if the condition cond
is true at first arrival. This is a bit counter-intuitive but may be wanted in some circumstances; i.e if a cut / subdivision of the task's runtime is due anyway.
If no yield should take place if the condition is yet true (behaviour like PT_WAIT_UNTIL
but with other returned state) use PT_WAIT_ASYIELD_WHILE
with the inverse condition.
pt | A pointer to the protothread control structure |
cond | The condition. |
#define PT_YIELD_WHILE | ( | pt, | |
condition | |||
) |
Yield while a condition is true.
This macro yields the protothread until and only if the specified condition is true.
condition
is false from start.pt | pointer to the protothread control structure |
condition | the condition (a boolean expression). |
#define PT_WAIT_ASYIELD_WHILE | ( | pt, | |
condition | |||
) |
Wait while a condition is true mimicked as yield.
This macro yields the protothread until and only if the specified condition is true.
condition
is false from start.pt | pointer to the protothread control structure |
condition | the condition (a boolean expression). |
#define PT_OR_YIELD_HERE | ( | pt | ) |
Yield only if a previous conditional wait did not.
This macro yields the protothread if and only if a previous conditional wait or wait_as_yield yield did not do so and just went ahead.
pt | pointer to the protothread control structure |
#define PT_OR_YIELD_REENTER | ( | ) |
Yield only if a previous conditional wait did not and re-enter that.
This macro yields if and only if a previous conditional wait or wait_as_yield macro did not do so and just went ahead. In that case this yields the protothread.
Contrary to the else similar PT_OR_YIELD_HERE the resume point for the next schedule is not at this macro's (PT_OR_YIELD_REENTER) call but (back) at the previous block or yield. The rationale lies in those cases where that previous block or yield macro's conditions must be rechecked or its other (side) effects have to be repeated.
Hint: As this behavior is not intelligibly to the reader of the source code a clarifying comment is strongly recommended:
PT_YIELD_OUT_SPACE(&cliThread->pt, 31, cliThread->repStreams); // (A) // if and only if (A) didn't yield, do yield here and now and PT_OR_YIELD_REENTER(); // re-enter at (A) above on next schedule