home *** CD-ROM | disk | FTP | other *** search
- /**********************************************************************
- *
- * NAME: task.cpp
- *
- * DESCRIPTION: routines for multitasking scheduler
- *
- * copyright (c) 1991 J. Alan Eldridge
- *
- * 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 JAE 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 moved semas to their own file
- *
- * 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 ...
- *
- * added new queue pZombies, and code in
- * Task::suicide() and scheduer() to support
- * this feature
- *
- * added CHECK_... defines to control
- * run-time checking for fatal errors
- * (calling TaskFatal() if necessary)
- *
- *********************************************************************/
-
- #include "aedef.h"
- #include "task.h"
-
- //------------------------------------------------------------
- // local defines to control error checking/reporting:
- // each of these enables to code to check for an error condition
- // and call TaskFatal() with a message if error occurred
- //------------------------------------------------------------
-
- #define CHECK_SUSPEND 1 // set to 1 to check for attempt to
- // suspend non-current task
-
- #define CHECK_STACK_OVER 1 // set to 1 to check for stack overflow
-
- #define CHECK_STACK_ALLOC 1 // set to 1 to check for failed attempt
- // to allocate a Task's stack
-
- #define CHECK_QUEUE_ALLOC 1 // set to 1 to check for failure to create
- // the three system task queues
-
- //------------------------------------------------------------
- // copyright notice: do not remove this definition!
- //------------------------------------------------------------
-
- static const char copyright[] = "Task++ v. " TASKPP_VERSION
- " Copyright (c) 1991 J. Alan Eldridge";
-
- //------------------------------------------------------------
- // Stack fill value
- //------------------------------------------------------------
-
- const uchar Task::stkval = 0xAE;
-
- //------------------------------------------------------------
- // ********** SCHEDULER **********
- //------------------------------------------------------------
-
- //------------------------------------------------------------
- // the scheduler's data storage is implemented entirely
- // by these static variables
- //------------------------------------------------------------
-
- // static SLList tasks; // list of Tasks
-
- // This variable "tasks" has been changed to a ptr to a
- // list of tasks. This was done at the suggestion of Peter W.
- // at Borland Tech Support. Here's the scenario, since
- // it is of interest to anyone creating global objects:
- //
- // The order of calling global object constructors inside one
- // module is well defined: top to bottom, left to right.
- // The order of calling the constructors in multiple modules
- // is "implementation-defined" and subject to change without
- // notice. That is, I cannot ensure that the constructor for
- // "tasks" is called before the constructors for any global Task
- // instances. The only way I can control when the constructor
- // is called is to allocate the list using operator new. Thus,
- // the ptr is initialized to 0 (explicitly, since we need to
- // make sure it is done before the startup code calls constructors
- // and zeros uninitialized globals). Then, the code to install
- // a task in the list (sch_AddTask()) checks the value, and
- // creates the list if it doesn't already exist. There is the
- // slight inefficiency caused by the extra level of indirection,
- // but this is the only reliable way to get the results I need.
-
- static SysPriQP *pRunning = 0; // ptr to list of running Tasks
- static SysQP *pBlocked = 0; // ptr to list of blocked Tasks
- static SysQP *pZombies = 0; // ptr to list of zombie Tasks
- // (scheduler will kill them...)
-
- static jmp_buf schEnv; // environment in scheduler
-
- //------------------------------------------------------------
- // the global CurrTask lets an application function know
- // who's currently executing
- //------------------------------------------------------------
-
- Task *CurrTask = 0;
-
- //------------------------------------------------------------
- // these 4 calls are the entire interface to the scheduler:
- // SchAddTask(), SchResume(), SchWakeup(), scheduler()
- //------------------------------------------------------------
-
- //------------------------------------------------------------
- // SchAddTask -- add a new task to the scheduler task list
- // WARNING: this should only be called by class Task constructor
- //------------------------------------------------------------
-
- inline void
- SchAddTask(Task *t)
- {
- if (!pRunning) {
- pRunning = new SysPriQP;
- pBlocked = new SysQP;
- pZombies = new SysQP;
- #if CHECK_QUEUE_ALLOC
- if (!(pRunning && pBlocked && pZombies))
- TaskFatal("can't create task queues!");
- #endif
- }
-
- pRunning->Add(t);
- }
-
- //------------------------------------------------------------
- // SchResume -- return to setjmp() in scheduler()
- //------------------------------------------------------------
-
- inline void
- SchResume(int code = 1)
- {
- longjmp(schEnv, code);
- }
-
- //------------------------------------------------------------
- // SchWakeup() -- wakeup any sleeping or blocked tasks
- //------------------------------------------------------------
-
- inline void
- SchWakeup()
- {
- int cnt = pBlocked->Cnt();
-
- while (cnt-- > 0) {
- Task *pt = GetNxtTask(*pBlocked);
-
- if (pt->maybeWake())
- pRunning->Add(pt);
- else
- pBlocked->Add(pt);
- }
- }
-
- //------------------------------------------------------------
- // scheduler() -- this is the main scheduler loop
- //
- // if the arg is 0, it means somebody wants to shut
- // down all tasks... we do so, then resume the scheduler
- // at the setjmp(), where it will return on its own stack
- //
- // otherwise... every time a task surrenders control it
- // returns to the setjmp() near the beginning. we then:
- //
- // check for stack overflow
- // if it's not a zombie...
- // place it on the right queue (ready or blocked)
- //
- // then we go into a simple loop:
- //
- // while (there are tasks left) loop
- // wakeup anybody who can be awakened
- // get next one off ready queue
- // if nobody ready, continue
- // if it hasn't been initialized,
- // initialize it
- // else
- // make it return to where it suspended itself
- // end loop
- //
- // returns 1 if forced down by scheduler(0), zero
- // if tasks terminated normally, and optionally calls TaskFatal()
- // if stack basher has been detected
- //
- //------------------------------------------------------------
-
- int
- scheduler(int tasking)
- {
- if (!tasking) {
- CurrTask = 0;
- SchResume(-1);
- }
-
- int code = setjmp(schEnv);
-
- // check state of CurrTask and put on a task queue
-
- if (code >= 0 && CurrTask) {
- #if CHECK_STACK_OVER
- if (!CurrTask->stackok()) {
- // die if we blew out the stack
- TaskFatal("stack overflow!");
- } else
- #endif
- {
- // add to appropriate queue
- if (CurrTask->isReady())
- pRunning->Add(CurrTask);
- else if (!CurrTask->isZombie())
- pBlocked->Add(CurrTask);
- else
- pZombies->Add(CurrTask);
- }
- }
-
- // clean up any zombies left around
-
- int zcnt = pZombies->Cnt();
-
- while (zcnt-- > 0) {
- Task *pt = GetNxtTask(*pZombies);
-
- if (pt->shouldDelete())
- delete pt;
- }
-
- // find next eligible task to run and set it off
-
- while (code >= 0 && (pRunning->Cnt() > 0 || pBlocked->Cnt() > 0)) {
- SchWakeup();
- if (!(CurrTask = GetNxtTask(*pRunning)))
- continue;
- if (!CurrTask->isInited())
- CurrTask->init();
- else
- CurrTask->resume(CurrTask->timeOut() ? -1 : 1);
- }
-
- CurrTask = 0;
- return code < 0;
- }
-
- //------------------------------------------------------------
- // ********** CLASS TASK MEMBER FUNCTIONS **********
- //------------------------------------------------------------
-
- //------------------------------------------------------------
- // constructor for a Task: set flags, allocate stack space
- //------------------------------------------------------------
-
- Task::Task(char *tname, int stk): stklen(stk), tskname(tname)
- {
- fInited = 0;
- fReady = 1;
- fTimed = 0;
- fZombie = 0;
- fDelete = 0;
-
- tskpri = 1;
- if (stk < StackMin)
- stk = StackMin;
- stack = new uchar [ stk ];
- #if CHECK_STACK_ALLOC
- if (!stack)
- TaskFatal("can't allocate stack for task %s!", tname);
- #endif
- memset(stack, stkval, stk);
- SchAddTask(this);
- }
-
- //------------------------------------------------------------
- // Task::timeOut() -- return & reset timed out flag
- //------------------------------------------------------------
-
- int
- Task::timeOut()
- {
- int timedOut = fTimed;
-
- fTimed = 0;
- return timedOut;
- }
-
- //------------------------------------------------------------
- // Task::init() -- initialize a task: switch stacks and
- // call the virtual TaskMain() function, never to return
- //------------------------------------------------------------
-
- // this function is used as a wrapper so that the "this"
- // pointer is saved on the stack; that way, if a task returns,
- // it immediately commits suicide
-
- static void
- callTaskMain(Task *t)
- {
- t->TaskMain(); // call the main function ...
- t->suicide(); // if it returns, the task wants to die
- }
-
- void
- Task::init()
- {
- fInited = 1; // we are initialized
-
- // switch to private stack now
-
- static jmp_buf stkswitch;
-
- if (!setjmp(stkswitch)) {
- #if defined(__TURBOC__)
- uchar far *farstk = (uchar far *)stack;
-
- stkswitch[ 0 ].j_ss = FP_SEG(farstk);
- stkswitch[ 0 ].j_sp = FP_OFF(farstk) + stklen;
- #elif defined(__GNUG__)
- stkswitch[ 0 ].esp = (unsigned int)stack + stklen;
- #endif
- longjmp(stkswitch, 1);
- }
-
- // start task running ...
- callTaskMain(CurrTask); // ... and never return
- }
-
- //------------------------------------------------------------
- // Task::stackok() -- have we blown out bottom of stack?
- //------------------------------------------------------------
-
- int
- Task::stackok()
- {
- const int StackChk = 64;
-
- for (int i = 0; i < StackChk; i++)
- if (stack[ i ] != stkval)
- return 0;
-
- return 1;
- }
-
- //------------------------------------------------------------
- // Task::suicide() -- become a zombie (scheduler will finish off)
- //------------------------------------------------------------
-
- void
- Task::suicide()
- {
- fReady = 0;
- fZombie = 1;
- if (this == CurrTask) // if we are the current task, then we
- suspend(); // aren't on any queues -- just go back
- else {
- pRunning->Del(this); // we're on one of these queues, so we
- pBlocked->Del(this); // just delete from both (one will succeed)
- pZombies->Add(this); // put on Zombie queue (scheduler will kill)
- }
- }
-
- //------------------------------------------------------------
- // Task::suspend() -- voluntarily give up control to scheduler
- //------------------------------------------------------------
-
- int
- Task::suspend()
- {
- #if CHECK_SUSPEND
- if (this != CurrTask)
- TaskFatal("suspend task <%s>: not running!", name());
- #endif
-
- int n;
-
- if (!(n = setjmp(tskEnv)))
- SchResume();
- return n;
- }
-
- //------------------------------------------------------------
- // Task::block() -- block for an event, possibly with
- // a timeout period specified in milliseconds...
- // since the PC clock ticks 18.2 times a second, the granularity
- // of these calls is not too good, but it'll have to do
- //------------------------------------------------------------
-
- inline clock_t
- msecToTicks(clock_t msec)
- {
- const clock_t msecPerTick = 10000 / 182;
-
- return (msec + msecPerTick / 2) / msecPerTick;
- }
-
- int
- Task::block(clock_t msec)
- {
- if (msec > -1) {
- wakeUp = clock() + msecToTicks(msec);
- fTimed = 1;
- }
- fReady = 0;
- return suspend() > 0;
- }
-
- //------------------------------------------------------------
- // Task::unblock() -- return to running queue
- //------------------------------------------------------------
-
- void
- Task::unblock()
- {
- fTimed = 0;
- fReady = 1;
- if (pBlocked->Del(this))
- pRunning->Add(this);
- }
-
- //------------------------------------------------------------
- // Task::maybeWake() -- check the clock, and if the sleeping
- // task has slept long enough, return TRUE to unblock
- //------------------------------------------------------------
-
- int
- Task::maybeWake()
- {
- return fReady = (fTimed && clock() >= wakeUp);
- }
-
-