NAME
task - coroutines in the C++ task library
SYNOPSIS
#include <task.h>
typedef int (*PFIO)(int,object*);
typedef void (*PFV)();
class object {
public:
// exported constants and types
enum objtype { OBJECT, TIMER, TASK, QHEAD, QTAIL, INTHANDLER };
// exported constructor
object();
// exported data members
object* o_next;
static PFIO error_fct;
// exported virtual functions
virtual objtype o_type();
virtual int pending();
virtual void print(int, int=0);
// print flags, used as arguments to print function
#define CHAIN 1
#define VERBOSE 2
#define STACK 4
// exported misc functions
void alert();
void forget(task*);
void remember(task*);
// exported static member functions
static int task_error(int, object*);
static task* this_task();
};
class sched: public object {
public:
// exported constants and types
enum statetype { IDLE=1, RUNNING=2, TERMINATED=4 };
protected:
// protected constructor
sched();
public:
// exported data members
static task* clock_task;
static PFV exit_fct;
// exported virtual functions
virtual int pending();
virtual void print(int, int =0);
virtual void setwho(object*);
// exported misc functions
void cancel(int);
int dont_wait();
sched* get_priority_sched();
int keep_waiting();
statetype rdstate();
long rdtime();
int result();
// exported static member functions
static long get_clock();
static sched* get_run_chain();
static int get_exit_status();
static void set_exit_status(int i);
static void setclock(long);
};
class task: public sched {
public:
// exported constants and types
typedef unsigned char _Uchar;
enum modetype { DEDICATED=1, SHARED=2 };
#define DEFAULT_MODE DEDICATED
#define SIZE 3000
protected:
// protected constructor
task(char* = 0, modetype = DEFAULT_MODE, int = SIZE);
public:
// exported data members
task* t_next;
_Uchar* t_name;
// exported virtual functions
virtual objtype o_type();
virtual void print(int, int =0);
virtual void setwho(object*);
// exported misc functions
void cancel(int);
void delay(int);
int preempt();
void resultis(int);
void sleep(object* =0);
void wait(object*);
int waitlist(object* ...);
int waitvec(object**);
object* who_alerted_me();
// exported static member function
static task* get_task_chain();
};
class timer: public sched {
public:
// exported constructor
timer(int);
// exported virtual functions
virtual objtype o_type();
virtual void print(int, int =0);
virtual void setwho(object*);
// exported misc functions
void reset(int);
};
DESCRIPTION
A task is an object with a control thread running its asso-
ciated program. More specifically, a user task is an object
of a type derived from class task. The constructor of that
object is the main program of the task. The task does not
survive the completion of the constructor.
The task system works in terms of operations which may be
performed immediately or which must wait, and objects which
are ready or pending. If a task requests a service which
may be performed immediately, or waits on an object which is
ready, the task continues execution. If the service cannot
be performed immediately or if the object being waited on is
not ready, the task is blocked. When a running task is
blocked, the scheduler selects the next task which is ready
to run and gives it control. A task is never preempted, but
may be blocked, thus allowing other tasks to run. Control
proceeds in a round-robin sequence. (It is possible for a
task to be given priority and moved to the head of the list
of tasks to be run.) Whenever an object becomes newly
ready, any tasks which were waiting for it are notified, and
those tasks become ready to run.
A task may be in one of three states:
RUNNING
Running now or ready to be run.
IDLE Not ready to run; waiting for some object.
TERMINATED
The task has finished and cannot be resumed. The
task's return value may be retrieved.
The task system is rooted in class object. Anything derived
from this class may be put on a queue or waited on. Virtual
member function pending() returns non-zero if the object is
not ready. Each type of object may have its own version of
this function, and thus its own criteria for determining
whether it is ready or pending, but there may be only one
such function.
Each object has a list of objects which are waiting on it,
the remember chain. When a task waits on an object which is
not ready, the task is suspended (IDLE) and added to the
object's remember chain. When the object next becomes ready
(not pending), each waiting task is notified via member
function object::alert(). This function changes the state
of these tasks to RUNNING and puts them back on the list of
ready tasks, the scheduler's run chain.
Class sched, derived from object, provides the functionality
common to task-like objects. The scheduling functions are
provided here. Rather than a separate scheduler, the
scheduling operations are part of class sched, and tasks
cooperatively provide scheduling by means of its member
functions. This class may not be instantiated, but may be
used only as the base class of tasks and timers.
Class sched also provides the facilities for the simulated
passage of time. Units of simulated time need have nothing
to do with real time. The simulated system clock is ini-
tialized to zero, and can be set at most once via
sched::setclock(). Thereafter, the clock advances only by
calls to task::delay(), whose parameter is the number of
simulated time units to delay. When that amount of simu-
lated time has elapsed, delay() returns. The call to
delay() causes the clock to advance to the earlier of the
amount of delay and the next simulated time at which some-
thing is scheduled to happen. The current value of the
clock may be read via function sched::getclock().
Class timer is a stripped-down task which simply delays the
specified amount of simulated time, then terminates. A
timer may be waited on, and when its time has elapsed any
waiting tasks are alerted. A timer can be either RUNNING or
TERMINATED, but never IDLE.
Class task is derived from sched and provides the basic
functionality of user tasks. No object of type task may be
instantiated. All user tasks must be derived directly from
class task; that is, you may not further derive from a user
task type. Examples:
task mytask; // ERROR: cannot create a task
object
class consumer : public task { ... } // OK
class glutton : public consumer { ... } // ERROR: can-
not derive again
The first error is diagnosed by the compiler, since the task
constructor is protected. The error in the third line can-
not be diagnosed, but the program will not work.
A task may not terminate by simply exiting the constructor,
nor may the function return mechanism be used to return a
value from a task. A task must instead set its return value
by calling either task::resultis() or task::cancel(). This
puts the task in the TERMINATED state. Other tasks may
retrieve the result value by calling task::result(), which
will suspend the calling task until the queried task has
terminated. Even if a task does not need to return a value,
it must call resultis() or cancel() to ensure the task is
properly terminated before it is destroyed.
The task constructor takes three optional arguments: a name,
a mode, and a stack size.
The name should be a character string allocated statically
or on the heap, not a character array local to some func-
tion. The name appears only in printouts, and has no effect
on the task operation.
The mode specifies what sort of stack the task will use.
The mode may be DEDICATED (the default) or SHARED. Usually
you want a dedicated stack, meaning the task has a reserved
area of memory for its own stack. Shared stacks are expen-
sive, because tasks may need to swap their own stacks
between the shared area and a save area as they become
active and inactive. If you have hundreds of small tasks,
it makes sense for them to share stack space.
The stack size argument specifies the maximum amount of
stack space the task may use. The default is 3000 bytes.
There is no foolproof test for overflowing the stack space,
and the stack cannot be dynamically expanded. The stack
size is checked when a task is swapped in, and overflow is a
fatal error. If the stack overflows during execution due to
depth of function call nesting and amount of local variable
storage, this may cause bizarre program behavior or a crash.
When you derive a class from task, its constructor becomes a
new task which runs along with others which may already
exist. Function main() becomes a task with the creation of
the first task in the system. If several tasks are started
sequentially in main(), the second task cannot be created
until the first becomes blocked and main() regains control
to create a new task, and so on for additional tasks. This
also means that main() must invoke resultis() or cancel()
after starting all its tasks, so that it doesn't exit and
terminate the entire program while tasks are running.
Any task constructor may itself create new tasks.
Class object
Class object has only one constructor, which takes no argu-
ments.
object o;
Constructs an object o (which is illegal, since the
constructor is protected), which is not on any list.
object *p = o.o_next;
An object may be on at most one list (run chain, queue,
etc) at at time. The next object on the list is
pointed to by public data member o_next. This member
will be zero if there is no next object, and, in par-
ticular, if the current object is not on any list.
objtype t = p->o_type();
This virtual function returns the enumeration constant
of type objtype corresponding to the type of object
which p points to.
int i = p->pending();
Returns non-zero (true) if the object pointed to by p
is not ready. Derived classes must define a suitable
version of this virtual function if they are to be
waited on. The default version in class object always
returns true (not ready). The other predefined classes
have their own versions of this function.
o.print(how);
Prints basic information about the object pointed to by
p on stdout (not cout). If the first int parameter,
how, has bit VERBOSE set, prints information about all
the tasks on the object's remember chain. The second
argument is for internal use and defaults to zero.
This virtual function is normally called by the print()
functions from derived classes.
o.alert()
Scans the remember chain of task o, changes each task
from IDLE to RUNNING mode and puts it on the run chain.
When a pending object is waited on, the waiting task is
made IDLE, taken off the run chain, and put on the
object's remember chain. When the object becomes no
longer pending, this function is called automatically
by the predefined classes to wake up all the waiting
tasks.
o.forget(p)
Removes all instances of the task pointed to by p from
the remember chain of task o.
o.remember(p)
Adds the task pointed to by p to the remember chain of
task o.
int user_err_func(int, object*);
object::error_fct = user_err_func;
Allows the user a measure of control over error
recovery. If a user error function is defined, it must
take an int parameter which is one of the error numbers
in the DIAGNOSTICS section below, and a pointer to an
object. The latter will be the object which called
function object::task_error(), or which is otherwise
responsible for the error, as explained below. Static
data member object::error_fct may be set to point to
such a user error function. If it is set, task_error()
will call the user function. If the user function
returns zero, task_error() will return, and the opera-
tion which resulted in an error will presumably be
retried. If the user function returns non-zero,
task_error() will call exit().
int i = o.task_error(err, p);
int i = object::task_error(err, p);
This static member function is called by task system
functions when a run-time error occurs. The first int
parameter is one of the error codes listed in the DIAG-
NOSTICS section below. The second parameter is
intended to be the object which called the function, or
may be zero if there is no identifiable object respon-
sible. If static data member error_fct is zero,
task_error() prints a message on stderr (not cerr) and
calls exit(err). If error_fct is non-zero, the user
function is called with parameters err and p. If the
user function returns 0, then so does task_error() .
Otherwise, it calls exit(err) as above. If this func-
tion returns 0, its caller is intended to retry the
operation which failed.
task *tp = o.this_task();
task *tp = object::this_task();
This static member function returns a pointer to the
task which is currently running.
Class Sched
Class sched provides the basic scheduling functions, and
classes task and timer are each derived from it. No object
of this class may be created; it serves only as a base
class.
sched s;
Constructs an object s of type sched (which is illegal,
since the constructor is protected). The constructor
has no arguments. The object is initialized to be IDLE
and have no delay.
task* tp = ...;
sched::clock_task = tp;
If static data member sched::clock_task is non-zero,
the task it points to will be scheduled before any
other task each time the clock advances. When the
current task, if any, is blocked, this clock task will
be resumed. The clock task must be IDLE when it is
resumed, and may place itself in that state by calling
task::sleep().
void user_exit_func(void);
sched::exit_fct = user_exit_func;
You may declare a function taking no parameters and
returning void and assign it to static data member
sched::exit_fct. This function will then be called
just before final exit from the task system.
int i = s.pending();
The version of this function for class sched returns
non-zero (true) if the object (a task or timer) is TER-
MINATED, and zero (false) otherwise.
s.print(how);
Prints data about the sched portion of the object on
stdout (not cout). The first int parameter, how, is
passed to object::print(). The second argument is for
internal use and defaults to zero. This virtual func-
tion is normally called by the print() functions from
derived classes.
tp->setwho(objp)
This virtual function is provided for classes task and
timer. See the writeup for those classes. It is an
error to call this version.
s.cancel(val);
Puts task or timer s in the TERMINATED state, and uses
int value val as its returned value. Unlike
resultis(), cancel() does not suspend the caller. This
enables one task to terminate another while retaining
control.
int i = s.dont_wait();
Returns the difference between the number of times
keep_waiting() and the number of times dont_wait() have
been called, not counting this call. The count is then
decremented. This count is kept in a static variable,
so it represents the system-wide difference. The count
is supposed to represent the number of objects which
are waiting for external events. See keep_waiting()
below.
sched *tp = s.get_priority_sched();
A special system task, the interrupt alerter, is
priority-scheduled when a signal has occurred which was
being waited for. This function returns a pointer to
that task if it has been scheduled, zero otherwise.
See also interrupt(3CC4).
int i = s.keep_waiting();
Returns the difference between the number of times
keep_waiting() and the number of times dont_wait() have
been called, including this call. That is, the count
is first incremented. This count is kept in a static
variable, so it represents the system-wide difference.
The count is supposed to represent the number of
objects which are waiting for external events. In par-
ticular, the constructor for class Interrupt_handler
calls keep_waiting() and the destructor calls
dont_wait(). The scheduler uses this information so
the task system doesn't exit when interrupt handlers
are active.
statetype t = s.rdstate();
Returns the state of task or timer s: RUNNING, IDLE,
or TERMINATED.
long l = s.rdtime();
Returns the simulated time at which task or timer s is
scheduled to run.
int i = s.result();
Returns the ``result'' of task s, which is set by
sched::cancel(), task::cancel(), or task::resultis().
If task s has not terminated, the calling task will be
suspended until it does terminate. It is an error for
a task to call result() on itself.
long l = s.getclock();
long l = sched::getclock();
Returns the value of the current simulated time.
sched* p = s.get_run_chain();
sched* p = sched::get_run_chain();
Returns a pointer to the run chain, the list of all
tasks and timers which are ready to run. These objects
are linked through the o_next field.
int i = s.get_exit_status();
int i = sched::get_exit_status();
When the task system exits normally (not because of a
call to task_error()), the value last given to
set_exit_status() is passed to the system exit() rou-
tine. This is by default zero, the indicator of
succesful completion. Function get_exit_status()
returns the value currently slated to be passed to
exit().
s.set_exit_status(i);
sched::set_exit_status(i);
The int value i is saved, and will be passed to the
system function exit() if the task system terminates
normally (not because of a call to task_error()). If
set_exit_status() is never called, that value will be
zero, the indicator of succesful completion. Other-
wise, the last value passed to set_exit_status() will
be used.
s.setclock(l);
sched::setclock(l);
Sets the simulated clock to long value l. System time
starts at zero by default. It is an error to call this
function more than once.
Class Task
task t(name, mode, size);
Constructs a task. The parameters are described above.
As also noted above, the constructor is protected, and
thus task may serve only as a base class for user
tasks.
task *tp = t.t_next;
The next task on the master list of all tasks. See
get_task_chain() below.
unsigned char* p = t.t_name;
A pointer to the name of the task as assigned by the
task constructor.
objtype o = t.o_type();
This virtual function defined in object returns the
kind of object. For a task, the object kind is TASK.
t.print(how);
Prints data about task t on stdout (not cout). The
first int parameter, how, may have any combination of
the VERBOSE and CHAIN bits set. If VERBOSE is set,
addtional information is printed. If CHAIN is set,
information on every task in the system is printed.
This parameter is passed to sched::print(). The second
argument is for internal use and defaults to zero.
t.setwho(objptr);
This virtual function remembers the object pointed to
by objptr as the object which alerted task t. The
intent is that task t was waiting on object *objptr,
and when it went from pending to ready, the object
alerted this task and changed its state from IDLE to
RUNNING. The object which was remembered may be
retrieved with who_alerted_me().
t.cancel(val);
Puts task t in the TERMINATED state, and uses int value
val as its returned value. Unlike resultis(), cancel()
does not suspend the caller. This enables one task to
terminate another while retaining control. See also
resultis().
t.delay(n);
Suspends task t for n simulated time units, leaving it
RUNNING. The RUNNING task with the least delay left
gets control, and the simulated clock is advanced to
its continuation time. When the clock has advanced by
n units, task t will continue execution. The use of
delay() is the only way to advance the clock.
int i = t.preempt();
Suspends RUNNING task t, making it IDLE. It returns
the number of time units left before t was scheduled to
continue execution. It is an error to call preempt()
for an IDLE or TERMINATED task.
t.resultis(val);
Puts task t in the TERMINATED state, and uses int value
val as its returned value. This value may be examined
by calling t.result(). Any tasks which have called
result() on task t and are thus waiting for the task to
terminate will be alerted. A task may not return a
value via the ordinary function return mechanism, but
must call either resultis() or cancel() to terminate.
Every task is pending until it is terminated.
t.sleep(objptr);
Unconditionally suspends task t, making it IDLE. The
optional argument points to an object that will
``remember'' task t and alert it when the object
becomes ready. Unlike wait(), sleep() does not first
check the object to see whether it is pending; the task
is always suspended.
t.wait(objptr);
Suspends task t (makes it IDLE) if the object pointed
to by objptr is pending. In this case, t will be
alerted by the object (made RUNNING and put back on the
run chain) when the object becomes ready. If the
object is not pending, task t is not suspended, but
retains control.
int which = t.waitlist(op1, op2, ... , NULL);
This function takes a list of pointers to objects, ter-
minated by a null pointer. If all of the objects
pointed to are pending, task t is suspended until one
of them becomes ready. Waitlist returns when at least
one of the tasks is ready, returning the index (start-
ing from 0) in the list of objects that caused the
return. Other objects in the list might also be ready
at this point. If none of the objects are pending as
of the call to waitlist(), task t is not suspended and
waitlist() returns immediately.
int which = t.waitvec(objarray);
This works in exactly the same way as waitlist(),
except that this function takes an an array of pointers
to objects instead of a variable-length list. The
array is terminated by a null pointer.
task *tp = t.get_task_chain();
task *tp = task::get_task_chain();
This static member function returns the head of the
list of all tasks. Every task in the system, RUNNING,
IDLE, and TERMINATED, is on this list, linked via the
t_next data member.
Class Timer
timer tm(d);
Constructs a timer which will expire in d simulated
time units. The timer is placed on the run chain.
objtype o = tm.o_type();
This virtual function defined in object returns the
kind of object. For a timer, the object kind is TIMER.
tm.print(how);
Prints data about timer tm on stdout (not cout). The
first int parameter, how, is passed to sched::print().
The second argument is for internal use and defaults to
zero.
tm.setwho(objptr);
This virtual function has no effect for timers, since
they cannot be alerted.
tm.reset(d);
Resets the delay of timer tm to d simulated time units.
A timer may be reset even if it has been terminated.
This means that a timer may be reused; they need not be
continually created and destroyed.
DIAGNOSTICS
When the task system detects a run time error, it calls
object::task_error() as described above. The following
table lists the possible error values, associated messages,
and meanings.
____________________________________________________________
Error Name Message Explanation
____________________________________________________________
E_ERROR (no message) Undefined error.
____________________________________________________________
E_OLINK object::delete(): Attempt to des-
has chain troy an object
which
``remembers''
one or more
objects.
____________________________________________________________
E_ONEXT object::delete(): on Attempt to des-
chain troy an object
which is on a
list.
____________________________________________________________
E_GETEMPTY qhead::get(): empty Attempt to get
from an empty
queue.
____________________________________________________________
E_PUTOBJ qtail::put(): object Attempt to put
on other queue an object on a
queue which is
already on a
queue.
____________________________________________________________
E_PUTFULL qtail::put(): full Attempt to put
an object on a
full queue.
____________________________________________________________
E_BACKOBJ qhead::putback(): Attempt to put
object on other back onto a
queue queue an object
which is already
on a queue.
____________________________________________________________
E_BACKFULL qhead::putback(): Attempt to put
full back an object
to a full queue.
____________________________________________________________
E_SETCLOCK sched::setclock(): Attempt to set
clock!=0 the clock twice.
____________________________________________________________
E_CLOCKIDLE sched::schedule(): The clock task
clock_task not idle was not IDLE
when it was
scheduled to be
run.
____________________________________________________________
| E_RESTERM | sched::insert(): | Attempt to |
| | cannot schedule ter- | resume a TER- |
| | minated sched | MINATED task. |
|_____________|_________________________|___________________|
| E_RESRUN | sched::schedule(): | Attempt to |
| | running | resume a RUNNING |
| | | task. |
|_____________|_________________________|___________________|
| E_NEGTIME | sched::schedule(): | Attempt to set |
| | clock<0 | the clock to a |
| | | negative value |
| | | or use a nega- |
| | | tive delay. |
|_____________|_________________________|___________________|
| E_RESOBJ | sched::schedule(): | Attempt to |
| | task or timer on | resume task |
| | other queue | already on a |
| | | queue. |
|_____________|_________________________|___________________|
| E_HISTO | histogram::histogram(): | Inconsistent or |
| | bad arguments | illegal argu- |
| | | ments to histo- |
| | | gram construc- |
| | | tor. |
|_____________|_________________________|___________________|
| E_STACK | task::task() or | The run time |
| | task::resume(): | task stack has |
| | stack overflow | overflowed. |
|_____________|_________________________|___________________|
| E_STORE | new: free store | No more free |
| | exhausted | store available |
| | | for task book- |
| | | keeping. |
|_____________|_________________________|___________________|
continued...
______________________________________________________________
Error Name Message Explanation
______________________________________________________________
E_TASKMODE task::task(): bad Illegal mode
mode arguments for
the task con-
structor.
______________________________________________________________
E_TASKDEL task::~task(): not Attempt to des-
terminated troy a non-
TERMINATED task.
______________________________________________________________
E_TASKPRE task::preempt(): not Attempt to
running preempt a non-
RUNNING task.
______________________________________________________________
E_TIMERDEL timer::~timer(): not Attempt to des-
terminated troy a non-
TERMINATED
timer.
______________________________________________________________
E_SCHTIME sched::schedule(): Run chain cor-
runchain corrupted: rupted, not in
bad time time order.
______________________________________________________________
E_SCHOBJ sched object used Attempt to use a
directly (not as sched object
base) rather than a
derived task or
timer.
______________________________________________________________
E_QDEL queue::~queue(): not Attempt to des-
empty troy a non-empty
queue.
______________________________________________________________
E_RESULT task::result(): A task attempted
thistask->result() to call result()
on itself.
______________________________________________________________
E_WAIT task::wait(): wait A task attempted
for self to wait on
itself.
______________________________________________________________
E_FUNCS FrameLayout::FrameLayout(): (not used)
function start
______________________________________________________________
E_FRAMES FrameLayout::FrameLayout(): (not used)
frame size
______________________________________________________________
E_REGMASK task::fudge_return():
unexpected register
mask
(not used)
______________________________________________________________
| E_FUDGE_SIZE| task::fudge_return(): | (not used) |
| | frame too big | |
|_____________|____________________________|__________________|
| E_NO_HNDLR | signal_handler - no | A signal |
| | handler for signal | occurred but no |
| | | handler was |
| | | registered. |
|_____________|____________________________|__________________|
| E_BADSIG | illegal signal | Attempt to |
| | number | register an |
| | | illegal signal |
| | | number. |
|_____________|____________________________|__________________|
| E_LOSTHNDLR | Interrupt_handler:: | Internal error: |
| | ~Interrupt_handler(): | a signal handler |
| | signal handler not | was lost. |
| | on chain | |
|_____________|____________________________|__________________|
| E_RUNCHAIN | sched::sched(): run | All tasks have |
| | chain empty | terminated and |
| | | there are no |
| | | interrupt |
| | | handlers. |
|_____________|____________________________|__________________|
NOTE
As of the date of this release, the coroutine library will
not be supported beyond the current version.
SEE ALSO
task.intro(3CC4), interrupt(3CC4), queue(3CC4),
tasksim(3CC4), exit(3C).