home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Black Box 4
/
BlackBox.cdr
/
progc
/
aetsk100.arj
/
TASK.H
< prev
next >
Wrap
C/C++ Source or Header
|
1991-12-01
|
16KB
|
489 lines
/**********************************************************************
*
* NAME: task.h
*
* DESCRIPTION: header for multi-tasking scheduler
*
* copyright (c) 1991 J. Alan Eldridge
*
* Warning: because of a bug in the Borland C++ clock() function,
* timeouts may not work correctly when crossing midnight boundaries.
*
* M O D I F I C A T I O N H I S T O R Y
*
* when who what
* -------------------------------------------------------------------
* 03/12/91 J. Alan Eldridge created
*
* Mar-May 91 J. Alan Eldridge many, many revisions
*
* 10/23/91 JAE added code to force task stack size
* up to reasonable minimum value
*
* 10/26/91 JAE added support for DJ Delorie's port
* port of GNU C++ v. 1.39
*
* 11/09/91 JAE rewrote to use SysQP class for
* Sema, Task queues
*
* tasks now can have priorities...
* (be careful of starvation!)
*
* 11/11/91 JAE added stack basher checking in class
* Task, called by scheduler
*
* 11/13/91 JAE removed obsolete queue types
*
* Semas can now be prioritized
* by Task priority (so the highest
* priority Task gets its wait() first)
* ... MBoxes also have this feature
*
* 11/16/91 jae rewrote pipes to use generic.h <arrgh!>
* (I just can't maintain two sets of source,
* one with templates and one without... when
* everybody is using C++ 3.0, I'll replace
* the generics with templates.)
*
* 11/21/91 jae added class ChildTask (so you can create
* a task from within another task, and
* then wait for it to finish before
* continuing)
*
* 11/30/91 jae added fDelete flag to Task, so you can
* dynamically create a new Task on the heap,
* and then proceed, knowing that the storage
* will eventually be freed when the task
* is killed or commits sucicide
*
*********************************************************************/
#ifndef __TASK_H
#define __TASK_H
#include <generic.h>
#define SUSPEND_ON_WAIT 0 // if 1, suspend on Sema.wait() even if
// resource is available (if 0, don't suspend)
#if !defined(__TURBOC__) && !defined(__GNUG__)
#error Turbo C++/Borland C++ or GNU C++ required!
#endif
class Task; // forward decl for typedefs
#include "sysqvfnc.h"
#define GetNxtTask(q) ((Task*)((q).Get()))
// Task++ version number
#define TASKPP_VERSION "3.10"
//------------------------------------------------------------
// ********** SCHEDULER **********
//
// the scheduler has only 1 public entry point:
// scheduler(arg) -- starts up the tasks (arg != 0)
// -- shuts down all tasks (arg == 0)
//------------------------------------------------------------
extern int scheduler(int tasking = 1);
//------------------------------------------------------------
// a ptr to the currently executing task is kept in CurrTask
//------------------------------------------------------------
class Task; // forward reference
extern Task *CurrTask;
//------------------------------------------------------------
// everything that takes a wait time argument can also give
// one of these values
//------------------------------------------------------------
#define NoWait 0L
#define WaitForever -1L
//------------------------------------------------------------
// ********** CLASS SEMAPHORE **********
//
// semaphores are used to control access to resources,
// such as the keyboard, a printer, etc.
//------------------------------------------------------------
class SemaBase {
private:
int s_value; // resource count
protected:
SysQP *s_waiting; // queue of tasks waiting
public:
SemaBase(int val = 0): s_value(val) { }
~SemaBase()
{ delete s_waiting; }
// check value
int avail()
{ return s_value > 0; }
operator int() // as in: if (sema) ...
{ return avail(); }
// wait on sema
int wait(clock_t msec = WaitForever);
void operator--()
{ wait(); }
#ifdef CPP_2_1
void operator--(int i)
{ wait(); }
#endif
// signal sema
void signal(int susp = 1);
void operator++()
{ signal(); }
#ifdef CPP_2_1
void operator++(int i)
{ signal(); }
#endif
};
class Sema: public SemaBase {
public:
Sema(int val=0): SemaBase(val)
{ s_waiting = new SysQP; }
};
class SemaPri: public SemaBase {
public:
SemaPri(int val=0): SemaBase(val)
{ s_waiting = new SysPriQP; }
};
//------------------------------------------------------------
// ********** CLASS TASK **********
//
// class Task implements the basic schedulable entity...
//------------------------------------------------------------
class Task: public Qable {
private:
friend class SemaBase;
friend int scheduler(int tasking);
friend void SchWakeup();
static const uchar stkval; // value to fill stack with
char *tskname; // name of task
// flags for task state
uint fInited:1, // initialized
fReady:1, // ready to run
fTimed:1, // blocked w/timeout
fZombie:1, // dead but not buried
fDelete:1; // should delete when dead
jmp_buf tskEnv; // environment for context switching
int stklen; // length of task's stack
uchar *stack; // this task's stack space
clock_t wakeUp; // when to wake up if asleep
int tskpri; // task priority
// get state of flags
int isInited()
{ return fInited; }
int isReady()
{ return fReady; }
int isTimed()
{ return fTimed; }
int isZombie()
{ return fZombie; }
int shouldDelete()
{ return fDelete; }
// clear fTimed flag and return previous value
int timeOut();
// set up stack and begin execution
void init();
// return to place of last suspend()
void resume(int code = 1)
{ longjmp(tskEnv, code); }
// possibly wake up a timed task
int maybeWake();
// if blocked, task will wait until another unblocks it
int block(clock_t msec = WaitForever);
// make task ready to execute again
void unblock();
// check if stack bashed
int stackok();
public:
// enum for minimum allowed stack size
#if defined(__TURBOC__)
#if defined(_Windows)
enum { StackMin = 0x2000 };
#else
enum { StackMin = 0x1000 };
#endif
#elif defined(__GNUG__)
enum { StackMin = 0x2000 };
#endif
// constructor, destructor
Task(char *tname, int stk = StackMin);
~Task()
{ delete stack; }
// get name of task
char *name()
{ return tskname; }
// set the delete flag
void setDelete()
{ fDelete = 1; }
// shut down one task
virtual void suicide();
// the main routine for each task
virtual void TaskMain() = 0;
// voluntarily give up control
int suspend();
// wait a specified amount of time
void sleep(clock_t msec)
{ block(msec); }
// get, set priority
int priority()
{ return tskpri; }
int priority(int newpri)
{ int oldpri = tskpri; tskpri = newpri; return oldpri; }
// compare tasks for priority
int operator<(Qable &t)
{ return tskpri < ((Task*)(&t))->tskpri; }
};
// these functions are for calls from non-member functions ...
// they are strictly for convenience in avoiding the CurrTask-> syntax
inline char *TaskName() { return CurrTask->name(); }
inline void TaskSuicide() { CurrTask->suicide(); }
inline int TaskSuspend() { return CurrTask->suspend(); }
inline void TaskSleep(clock_t msec) { CurrTask->sleep(msec); }
inline int TaskPriority() { return CurrTask->priority(); }
inline int TaskPriority(int newpri){ return CurrTask->priority(newpri); }
// kill an arbitrary task, including the current one
inline void TaskKill(Task *t) { t->suicide(); }
// report a fatal error and die with a call to exit(1)
// NOTE: the user is free to replace this routine if desired
void TaskFatal(char *fmt, ...);
//------------------------------------------------------------
// ********** CLASS CHILDTASK **********
//
// this is a class that implements a child task: the parent waits
// for the child to die before continuing...
//
// e.g.:
//
// ChildTaskDerived *pcht; // ptr to child task
//
// pcht = new ChildTaskDerived("MyChild"); // create new task
// pcht->wait(); // wait for new task to die
// delete pcht; // destroy new task (now that it's dead)
//------------------------------------------------------------
class ChildTask: public Task, public Sema {
public:
ChildTask(char *tname, int stk=StackMin): Task(tname,stk)
{ }
virtual void suicide()
{ signal(0); Task::suicide(); }
};
//------------------------------------------------------------
// ********** CLASS MBOX **********
//
// class MBox implements a mailbox: this is an intertask
// communication device that implements a rendezvous type
// of interaction; that is, a send must block until a receiver
// has picked up the message. A sender or receiver may specify
// a timeout period.
//
// Note that class MBoxPri (a mailbox with priority semaphores)
// may result in rather weird behavior if you have more than
// one receiver (the highest priority receiver will get the
// message from the highest priority sender, which may end up
// seeming unpredictable). I don't recommend using multiple
// receivers for mailboxes, priority or not.
//------------------------------------------------------------
class MBoxBase {
private:
int m_len; // length of msg
void *m_msg; // ptr to msg data
Sema s_pickup; // wait for receiver's pickup sema
protected:
SemaBase *s_send; // wait to send sema
SemaBase *s_recv; // wait to receive sema
public:
// constructor
MBoxBase()
{ }
~MBoxBase()
{ delete s_send; delete s_recv; }
// send a message to mailbox
int send(void *msg, int n, clock_t msec = WaitForever);
// receive a message from mailbox
int recv(void *msg, clock_t msec = WaitForever);
};
class MBox: public MBoxBase {
public:
MBox()
{ s_send = new Sema(1); s_recv = new Sema; }
};
class MBoxPri: public MBoxBase {
public:
MBoxPri()
{ s_send = new SemaPri(1); s_recv = new SemaPri; }
};
//------------------------------------------------------------
// ********** GENERIC CLASSES PIPE & CPIPE **********
//
// Classes Pipe and CPipe implement an in memory queue of
// objects... there are no priorities associated with items.
//
// Class Pipe should be used only for C++ builtin data types,
// or for structs/classes with no destructor (and the default
// assignment operator).
//
// Class CPipe should be used for objects which have both
// an assignment operator and an explicit destructor. Failure to do
// so can result in all sorts of antisocial behavior, including
// memory leaks (allocated blocks that will never be freed).
// (Note: if you use a CPipe, the class MUST have a destructor,
// because I call it explicitly when I take an object off the pipe.)
//
// To be placed on a CPipe, an object should have defined:
//
// 1. a default constructor with no args
// 2. an assignment operator taking an argument of type "type&"
// 3. an explicit destructor
//
// When receiving from the CPipe, you'll need a variable of the
// appropriate type... it will have been initialized by the default
// constructor. The receive function will destruct it before doing
// the assignment. If you timeout on a receive, it will not have
// been destroyed, so that we keep an even balance of constructors
// (assignments) and destructors. (I know this is a little complex,
// but I had to figure it out by experiment myself... it's not that
// easy to figure out what calls the compiler is going to generate.)
//
// Declaration syntax is:
//
// (to generate data types)
// declare(Pipe_, type);
// declare(CPipe_, type);
//
// (to generate member functions)
// implement(Pipe_, type);
// implement(CPipe_, type);
//
// (to declare variables)
// Pipe(type) var1;
// CPipe(type) var2;
//
// Just as with mailboxes, I don't recommend multiple receivers
// for pipes. It's rather hard to figure out what will go where.
//------------------------------------------------------------
#define Pipe(T) name2(Pipe_,T)
#define CPipe(T) name2(CPipe_,T)
#ifndef CPP_2_1
// C++ 2.0 needs array size to delete
#define P_MAX_ p_max
#else
// C++ 2.1 doesn't want array size for delete
#define P_MAX_
#endif
// both Pipe and CPipe have the same data structures and interface
#define PIPE_INTERNAL_(ptype,T) \
private: \
int p_max, p_head, p_tail; \
Sema s_send, s_recv; \
T *p_mem; \
public: \
ptype(int n): s_send(n) \
{ p_max = n; p_head = p_tail = 0; p_mem = new T [ n ]; } \
~ptype() \
{ delete [ P_MAX_ ] p_mem; } \
int send(T &s, clock_t msec = WaitForever); \
int operator<<(T &s) \
{ return send(s); } \
int recv(T &s, clock_t msec = WaitForever); \
int operator>>(T &s) \
{ return recv(s); }
// the send function is the same for both Pipe and CPipe except
// that for a CPipe, we destroy the current array element before
// assigning the new value
#define PIPE_SEND_INTERNAL_(dtorcall) \
if (!s_send.wait(msec)) \
return 0; \
dtorcall; \
p_mem[ p_tail++ ] = t; \
if (p_tail == p_max) \
p_tail = 0; \
s_recv.signal(); \
return 1;
// the receive function is identical for Pipe and CPipe, except
// for CPipe we destruct the target object before assigning to it
#define PIPE_RECV_INTERNAL_(dtorcall) \
if (!s_recv.wait(msec)) \
return 0; \
dtorcall; \
t = p_mem[ p_head++ ]; \
if (p_head == p_max) \
p_head = 0; \
s_send.signal(); \
return 1;
#define Pipe_declare(T) \
class Pipe(T) { \
PIPE_INTERNAL_(Pipe(T),T) \
}
#define Pipe_implement(T) \
int Pipe(T)::send(T &t, clock_t msec) \
{ \
PIPE_SEND_INTERNAL_((void)0) \
} \
int Pipe(T)::recv(T &t, clock_t msec) \
{ \
PIPE_RECV_INTERNAL_((void)0) \
}
declare(Pipe_,uchar);
declare(Pipe_,ushort);
typedef Pipe(uchar) BPipe;
typedef Pipe(ushort) WPipe;
#define CPipe_declare(T) \
class CPipe(T) { \
PIPE_INTERNAL_(CPipe(T),T) \
}
#define CPipe_implement(T) \
int CPipe(T)::send(T &t, clock_t msec) \
{ \
PIPE_SEND_INTERNAL_(p_mem[ p_tail ].T::~T()) \
} \
int CPipe(T)::recv(T &t, clock_t msec) \
{ \
PIPE_RECV_INTERNAL_(t.T::~T()) \
}
#endif