|
|
#include <task.h>typedef int (*PFIO)(int,object*); typedef void (*PFV)(); extern int _hwm;
class object { public: enum objtype { OBJECT, TIMER, TASK, QHEAD, QTAIL, INTHANDLER };
object* o_next; object(); ~object(); void alert(); void forget(task*); virtual objtype o_type(); virtual int pending(); virtual void print(int, int =0); void remember(task*); static int task_error(int, object*); int task_error(int); static task* this_task(); static PFIO error_fct; };
class sched : public object { protected: sched(); public: enum statetype { IDLE=1, RUNNING=2, TERMINATED=4 };
static task* clock_task; void cancel(int); int dont_wait(); static long get_clock(); sched* get_priority_sched(); static sched* get_run_chain(); static int get_exit_status(); int keep_waiting(); int pending(); void print(int, int =0); statetype rdstate(); long rdtime(); int result(); void setclock(long); static void set_exit_status(int); virtual void setwho(object*); static PFV exit_fct; };
#define DEFAULT_MODE DEDICATED
class task : public sched { public: enum modetype { DEDICATED=1, SHARED=2 }; protected: task(char* name=0, modetype mode=DEFAULT_MODE, int stacksize=SIZE); public: task* t_next; char* t_name; ~task(); void cancel(int); void delay(long); static task* get_task_chain(); objtype o_type(); long preempt(); void print(int, int =0); void resultis(int); void setwho(object*); void sleep(object* =0); void wait(object*); int waitlist(object* ...); int waitvec(object**); object* who_alerted_me(); };
class timer : public sched { public: timer(long); ~timer(); objtype o_type(); void print(int, int =0); void reset(long); void setwho(object*); };
task
is an object with an associated program
and thread of control.
To use the task system, the programmer must derive a class from class
task
,
and supply a constructor for the derived class
to serve as the task
's main program.
(Note, however, that only one level of derivation is permitted
from class task
.
See the ``Bugs'' section.)
Control in the task system is based on a concept of operations
which may succeed immediately or be blocked,
and object
s which may be
ready or pending (not ready).
When a task
executes a blocking operation
on an object
that is ready,
the operation succeeds immediately
and the task
continues running,
but if the object
is pending, the task
waits.
Control then returns to the scheduler,
which chooses the next task
from the ready list or
run chain.
Eventually, the pending object
may become ready,
and it will notify all the task
s that are waiting for it,
causing the waiting task
s to be put back on the
run chain.
A task
can be in one of three states:
task
is running or it is ready to run.
task
is waiting for a pending object
.
task
has completed its work.
It cannot be resumed,
but its result can be retrieved.
The function
sched::rdstate()
returns the state.
These states are enumerations of type statetype
.
These enumerations are in the scope of class sched
.
Most classes in the task system are derived from class object
.
Each different kind of object
can have its own way of determining
whether it is ready,
which makes it easy to add new capabilities to the system.
However, each kind of object
can have only one criterion
for readiness (although it may have several blocking operations).
The criterion for readiness is defined by the
virtual function
pending()
.
For all classes derived from object
,
pending()
returns TRUE if the object
is not ready.
This invariant should be maintained for user-defined derived classes as well.
Each pending object
contains a list (the
remember chain)
of the task
s that are waiting for it.
When a task
attempts an operation on a pending object
(that is, it calls a blocking function),
that task
is put on the
remember chain
for the object
via object::remember()
,
and the task
is suspended.
When the state of an object
changes from pending to ready,
object::alert()
must be called for the object
.
(Note, this is done for classes in the task system.
Programmers who write classes for which task
s can wait,
must ensure that object::alert()
is called on a state change.)
alert()
changes the state of all task
s ``remembered'' by the object
from IDLE to RUNNING
and puts them on the scheduler's
run chain.
The base class, sched
,
is responsible for scheduling and for the functionality
that is common to task
s and timer
s.
Class sched
can only be used as a base class,
that is, it is illegal to create objects of class sched
.
Class sched
also provides facilities
for measuring simulated time.
A unit of simulated time can represent any amount of real time,
and it is possible to compute without consuming simulated time.
The system clock is initialized to 0 and can be set with
sched::setclock()
once only.
Thereafter, only a call to task::delay()
will cause the clock to advance. sched::getclock()
can be used to read the clock.
Class timer
provides a facility for implementing time-outs
and other time-dependent phenomena.
A timer
is similar to a task
with a constructor consisting
of the single statement:
delay(d);That is, when a
timer
is created it simply waits for the number
of time units given to it as its argument,
and then wakes up any task
s waiting for it.
A timer
's state can be either RUNNING or
TERMINATED.
A task
cannot return a value with the usual function return
mechanism.
Instead, a task
sets the value of its
result
(using task::resultis()
or
task::cancel()
),
at which time the task
becomes TERMINATED.
Then this result can be retrieved by other task
s
via a call to sched::result()
.
The task
constructor takes three optional arguments:
a name, a mode, and a stacksize.
The name is a character string pointer,
which is used to initialize the class task
variable t_name
.
This name can be used to provide more readable output
and does not affect the behavior of the task
.
The mode argument can be DEDICATED (the default when none is specified)
or SHARED,
(the enumerations of type modetype
in class task
's scope).
DEDICATED task
s each have their own stack,
allocated from the free store.
SHARED task
s share stack space with the task
that creates them.
When a SHARED task
is running, it occupies the shared stack space,
while copies of the active portions of the other task
s' stacks
occupy save areas.
SHARED task
s trade speed for space:
they use less storage than DEDICATED task
s use,
but task switches among SHARED task
s often involve
copying stacks to and from the save area.
The stacksize argument to the task
constructor
represents the maximum space that a task
's stack can occupy.
The default is 750 machine words.
Overflowing the stack is a fatal error.
When an object of a class derived from class task
is created,
its constructor becomes a new task
that runs in
parallel with the other task
s that have been created.
When the first task
is created,
main()
automatically becomes a task
itself.
The public member functions supplied in the task system classes
task
, object
, sched
, and timer
are listed and described in the next four sections.
The following symbols are used:
task
object
object
object
sched
object
timer
object
object
task
sched
char
int
s
long int
objtype
enumeration
statetype
enumeration
modetype
enumeration
task
has one form of constructor, which is protected:
task
t(
cp,
em,
j)
task
object, t.
All three arguments are optional and have default values.
If cp is given, the character string it points to is used
as t's name.
em represents the mode (see above),
and can be DEDICATED or SHARED.
DEDICATED is the default.
The default mode can be changed to SHARED by recompiling
the task library with _SHARED_ONLY defined.
See the ``Notes'' section.
j represents the maximum size of t's stack;
the default is 750 machine words.
Most public member functions of class task
are conditional
or unconditional requests for suspension.
They are (in alphabetical order):
.cancel(
i)
task
(that is, without invoking the scheduler),
and sets t's result (or ``return value'') to i.
.delay(
l)
task
is in the RUNNING state.
t will resume at the current time on the task system clock
+ l.
Only a call to delay()
causes the clock to advance.
= task::get_task_chain()
=
t.get_task_chain()
task
on the list of all task
s
(linked by t_next
pointers).
=
t.o_type()
object::TASK
).
o_type()
is a virtual function.
=
t.preempt()
task
t, making it IDLE.
Returns the number of time units left in t's delay.
Calling preempt()
for an IDLE or TERMINATED task
causes a runtime error.
.print(
i)
task
s,
or STACK, for information about the runtime stack.
These argument constants can be combined with the or operator,
e.g., print(VERBOSE|CHAIN)
.
A second integer argument is for internal use and defaults to 0.
print()
is a virtual function.
.resultis(
i)
.result()
(result()
is a member function of class sched
).
task
s cannot return a value using the usual function return mechanism.
A call to task::resultis()
should appear at the end of every task
constructor body
(unless the constructor will execute infinitely).
A task
is pending
(see sched::pending()
)
until it is TERMINATED.
.setwho(
op)
object
denoted by op
as the one that alerted t when it was IDLE.
*
op
is meant to be the object
whose state change from pending to ready
caused t to be put back on the
run chain.
This information can be retrieved with
task::who_alerted_me()
.
.sleep(
op)
.sleep()
task::sleep()
is given a pointer to a pending object
as an argument,
t will be ``remembered'' by the denoted object
,
so that when that object
becomes ready,
t will be ``alerted''
and put back on the run chain
(via object::alert()
).
If no argument is given to
task::sleep()
,
the event that will cause t to be resumed is unspecified.
Contrast sleep()
with wait()
,
which suspends a task
conditionally.
task::sleep()
does not check whether the object
denoted by op is pending.
.wait(
op)
object
,
then t will be suspended (put in the IDLE state)
until that object
is ready.
If op points to an object
that is not pending (that is ready),
then t will not be suspended at all.
Any class derived from class object
that is ever going
be waited for must have rules for when it is pending and ready.
Each object
can only have one definition of pending.
=
t.waitlist(
op ...)
object
s to become ready.
waitlist()
takes a list of object
pointers terminated by a 0 argument.
If any of the arguments points to a ``ready'' object
,
then t will not be suspended at all.
waitlist()
returns when one of the object
s on the list is ready.
It returns the position in the list of the object
that caused the return,
with positions numbered starting from 0.
Note that object
s on the list other than the one denoted
by the return value might also be ready.
=
t.waitvec(
op*)
waitlist()
,
except that it takes as an argument the address of a vector
holding a list of object
pointers.
=
t.who_alerted_me()
object
whose state change
from pending to ready caused t
to be put back on the run chain
(put in the RUNNING state).
_hwm = 1;
task
's stack;
that is, the most stack ever used by each task
.
This information is printed by
task::print(STACK)
.
This information is intended primarily for debugging purposes,
and will affect performance speed.
_hwm
must be set before any task
s whose high water marks
are of interest are created.
Note that two task
s are created by a static constructor:
the internal Interrupt_alerter task
and the ``main'' task
.
If you need accurate information about the high water mark for ``main,''
then _hwm
must be set by a static constructor which is called
before that for the Interrupt_alerter
task
.
object
has one form of constructor:
object
o;
object
object, o, which is not on any lists.
The constructor takes no arguments.
Public member functions of class object
are
(in alphabetical order):
.alert()
task
s ``remembered'' by o
from IDLE to RUNNING,
puts them on the scheduler's
run chain,
and removes them from o's remember chain.
.forget(
tp)
task
denoted by tp
from o's
remember chain.
=
o.o_type()
object::OBJECT
). o_type()
is a virtual function.
=
o.pending()
object
.
It returns FALSE if o is ready, and TRUE if it is pending.
Classes derived from class object
must define pending()
if they are to be waited for. object::pending()
returns TRUE by default. pending()
is a virtual function.
.print(
i)
print()
functions for classes derived from
object
.
See task::print()
for a description of the arguments.
print()
is a virtual function.
.remember(
tp)
task
denoted by tp
to o's remember chain.
Remembered task
s will be alerted
when o's state becomes ready.
= object::task_error(
i,
op)
=
o.task_error(
i,
op)
object
which called task_error()
or 0.
object::task_error()
examines the variable error_fct
,
and if this variable denotes a function,
that function will be called with i
and op as arguments, respectively.
(See error_fct
, below.)
Otherwise, i will be given as an argument to
print_error()
,
which will print an error message on stderr
and call exit(
i)
, terminating the program.
The non-static, single argument form of task_error()
is obsolete, but remains for compatibility.
= object::this_task()
=
o.this_task()
task
that is currently running.
PFIO
user-defined-error-function;
error_fct =
user-defined-error-functionerror_fct
is a pointer to a function that returns an int
and takes two arguments: an int
representing the error number
and an object*
representing the object*
that called
task_error
.
If error_fct
is set,
task_error()
will call the user-defined-error-function with
the error number and the object*
as arguments.
(The object*
will be 0 if task_error
was not called by
an object
.)
If user-defined-error-function does not return 0,
task_error()
will call
exit(
i)
.
If the
user-defined-error-function
does return 0,
task_error()
will retry the operation that caused the error.
task
and class timer
are derived
from class sched
.
Class sched
provides one form of constructor,
which is protected:
sched
s;
sched
object, s,
initialized to be IDLE and to have a 0 delay.
Class sched
is responsible for the functionality that
is common to task
s and timer
s.
Class sched
provides the following public member functions:
.cancel(
i)
=
s.dont_wait()
keep_waiting()
has been called, minus the number of times
dont_wait()
has been called (excluding the current call).
If these functions are used as intended,
the return value represents
the number of object
s that were waiting for external events
before the current call.
See keep_waiting()
.
See
interrupt(C++)
for a description of how task
s can wait
for external events.
= sched::get_clock()
=
s.get_clock()
= sched::get_exit_status()
=
s.get_exit_status()
exit status
of the task program.
When a task program terminates normally (that is, task_error
is not called), the program will call exit(
i)
,
where i is the value passed by the last caller
of sched::set_exit_status()
.
=
s.get_priority_sched()
task
, interrupt_alerter
,
if a signal that was being waited for has occurred.
If no interrupt has occurred, get_priority_sched()
returns 0.
= sched::get_run_chain()
=
s.get_run_chain()
sched
objects
(task
s and timer
s).
=
s.keep_waiting()
keep_waiting()
has been called (not counting the current call), minus the number of times
dont_wait()
has been called.
keep_waiting()
is meant to be called when an object
that will wait
for an external event is created.
For example, it is called when an
Interrupt_handler
object is created by the
Interrupt_handler
constructor
(see
interrupt(C++)).
The inverse function, dont_wait()
,
should be called when such an object
is deleted.
keep_waiting()
causes the scheduler to keep waiting (not to exit) when there
are no runnable task
s
(because an external event may make an IDLE task
runnable).
=
s.pending()
task
or timer
)
is in the TERMINATED state,
TRUE otherwise.
pending()
is a virtual function.
.print(
i)
print()
functions for classes derived from sched
.
See task::print()
and timer::print()
for a description of the arguments.
print()
is a virtual function.
=
s.rdstate()
=
s.rdtime()
=
s.result()
task::resultis()
, task::cancel()
,
or sched::cancel()
).
If s is not yet TERMINATED,
the calling task
will be suspended
to wait for s to terminate.
If a task
calls result()
for itself, it will cause a run time error.
sched::setclock(
l)
.setclock(
l)
sched::set_exit_status(
i)
.set_exit_status(
i)
task_error
is not called), the program will call exit(
i)
,
where i is the value passed by the last caller
of set_exit_status()
.
.setwho(
op)
task
s and timer
s; see its
definition for those classes.
The argument is meant to be a pointer to the object
that caused s to be alerted.
PFV
user-defined-exit-function;
exit_fct =
user-defined-exit-functionexit_fct
is a pointer to a function taking no arguments and returning void.
If set,
the task system scheduler will call the user-defined-exit-function
before the program exits.
clock_task =
tp;
task
that
will be scheduled each time the system clock advances,
before any other task
s.
The clock_task
must be IDLE when it is resumed
by the scheduler.
The clock_task
can suspend itself by calling
task::sleep()
to ensure this.
timer
provides one form of constructor:
timer
tm(
l);
timer
object, tm,
and inserts it on the scheduler's
run chain.
l represents the number of time units tm is to wait.
The following public member functions are provided for timer
s:
=
tm.o_type()
object::TIMER
).
o_type()
is a virtual function.
.reset(
l)
timer
s possible.
A timer
can be reset even when it is TERMINATED.
.setwho(
op)
timer
s.
setwho()
is a virtual function.
.print(
i)
print()
is a virtual function.
task
!
Although the task library was engineered to be as free as possible
from dependencies on compilation systems
and dynamic call chains,
it does depend on the existence of stack frames for the task
constructor and constructors for classes derived from class task
.
If these constructors are inlined by an optimizing compiler,
unpredictable behavior will result.
For related reasons,
although you must derive a class from class task
to use the task library,
you can only have one level of derivation from class task
.
That is, the system will not work reliably if you derive a class
from a class derived from class task
. A workaround is suggested
below.
object::task_error()
,
with one of the following error numbers as an argument.
The table below lists the run time errors the task system detects,
the associated error messages, and explanations of the errors.
Error name | Message | Explanation |
---|---|---|
1 E_OLINK | object::delete(): has chain | Attempt to delete an object which remembers a task. |
2 E_ONEXT | object::delete(): on chain | Attempt to delete an object which is still on some chain. |
3 E_GETEMPTY | qhead::get(): empty | Attempt to get from an empty queue in E_MODE. |
4 E_PUTOBJ | qtail::put(): object on other queue | Attempt to put an object already on some queue. |
5 E_PUTFULL | qtail::put(): full | Attempt to put to a full queue in E_MODE. |
6 E_BACKOBJ | qhead::putback(): object on other queue | Attempt to putback an object already on some queue. |
7 E_BACKFULL | qhead::putback(): full | Attempt to putback to a full queue in E_MODE. |
8 E_SETCLOCK | sched::setclock(): clock!=0 | Clock was non-zero when setclock() was called. |
9 E_CLOCKIDLE | sched::schedule(): clock_task not idle | The clock_task was not IDLE when the clock was advanced. |
10 E_RESTERM | sched::schedule: terminated | Attempt to resume a TERMINATED task. |
11 E_RESRUN | sched::schedule: running | Attempt to resume a RUNNING task. |
12 E_NEGTIME | sched::schedule: clock<0 | Negative argument to delay(). |
---|---|---|
13 E_RESOBJ | sched::schedule: task or timer on other queue | Attempt to resume task or timer already on some queue. |
14 E_HISTO | histogram::histogram(): bad arguments | Bad arguments for histogram constructor. |
15 E_STACK | task::restore(): stack overflow | Task run time stack overflow. |
16 E_STORE | new: free store exhausted | No more free store -- new() failed. |
17 E_TASKMODE | task::task(): bad mode | Illegal mode argument for task constructor. |
18 E_TASKDEL | task::~task(): not terminated | Attempt to delete a non-TERMINATED task. |
19 E_TASKPRE | task::preempt(): not running | Attempt to preempt a non-RUNNING task. |
20 E_TIMERDEL | timer::~timer(): not terminated | Attempt to delete a non-TERMINATED timer. |
21 E_SCHTIME | schedule: bad time | Scheduler run chain is corrupted: bad time. |
22 E_SCHOBJ | sched object used directly (not as base) | Sched object used directly instead of as a base class. |
---|---|---|
23 E_QDEL | queue::~queue(): not empty | Attempt to delete a non-empty queue. |
24 E_RESULT | task::result(): thistask->result() | A task attempted to obtain its own result(). |
25 E_WAIT | task::wait(): wait for self | A task attempted to wait() for itself to TERMINATE. |
26 E_FUNCS | FrameLayout::FrameLayout(): function start | Internal error -- cannot determine the call frame layout. |
27 E_FRAMES | FrameLayout::FrameLayout(): frame size | Internal error -- cannot determine frame size. |
28 E_REGMASK | task::fudge_return(): unexpected register mask | Internal error -- unexpected register mask. |
29 E_FUDGE_SIZE | task::fudge_return(): frame too big | Internal error -- fudged frame too big. |
30 E_NO_HNDLR | sigFunc - no handler for signal | No handler for the generated signal. |
31 E_BADSIG | illegal signal number | Attempt to use a signal number that is out of range. |
32 E_LOSTHNDLR | Interrupt_handler:: | |
~Interrupt_handler(): | ||
signal handler not on chain |
task
s cannot be used on these machines with SVR2.
In such cases, compile the task library with _SHARED_ONLY defined,
which will make SHARED the default mode for tasks
.
(Note: it is insufficient to declare all task
s as SHARED without
compiling a _SHARED_ONLY version of the task library,
because there is an internal system task
(the interrupt alerter task
, see
interrupt(C++))
which is DEDICATED by default.)
UNIX System V Releases 3.1 and 3.2 (SVR3.1 and
SVR3.2)
for the Intel 386 machine
will not call a signal handler when the current task
is running on
a stack in the free store,
that is, when the current task
has a DEDICATED stack.
If you need to use the signal handling mechanisms
(described on the
tasksim(C++)
manual page) on that configuration,
you cannot use tasks which have DEDICATED stacks.
In this case, compile the task library with _SHARED_ONLY defined,
which will make SHARED the default mode for tasks
.
For implementation reasons, it is not possible to derive a class
from a class derived from class task
;
only one level of derivation is permitted.
Use of multi-level derivation is not detected,
and will usually result in an unexpected core dump.
One possible workaround for this limitation
is to put the required complex structures in a class not derived
from task
. Then derive a trivial class from task
whose
constructor executes the co-routine in the complex task. For example:
class Task_base { virtual int Main(); };Classclass Runner : public task { Task_base* p; public: Runner(Task_base*); };
Runner::Runner(Task_base* fp) : p(fp) { resultis(p->Main()); }
Task_base
is the base class from which the user should
derive whatever additional classes and structures are needed.
``A set of C++ classes for co-routine style programming,'' by Stroustrup, B. and Shopiro, J. E., in Chapter 2 of the C++ Library Manual.
``Extending the C++ task system for real-time control,'' by Shopiro, J. E., in Chapter 2 of the C++ Library Manual.
``A porting guide for the C++ co-routine library,'' by Keenan, S. A., in Chapter 2 of the C++ Library Manual.