home *** CD-ROM | disk | FTP | other *** search
/ C!T ROM 2 / ctrom_ii_b.zip / ctrom_ii_b / PROGRAM / C / AETSK101 / TASK.CC < prev    next >
C/C++ Source or Header  |  1991-12-01  |  15KB  |  458 lines

  1. /**********************************************************************
  2.  *  
  3.  *  NAME:           task.cpp
  4.  *  
  5.  *  DESCRIPTION:    routines for multitasking scheduler
  6.  *  
  7.  *  copyright (c) 1991 J. Alan Eldridge
  8.  * 
  9.  *  M O D I F I C A T I O N   H I S T O R Y
  10.  *
  11.  *  when        who                 what
  12.  *  -------------------------------------------------------------------
  13.  *  03/12/91    J. Alan Eldridge    created
  14.  *  
  15.  *  Mar-May 91  JAE                 many, many revisions
  16.  *                                  
  17.  *  10/23/91    JAE                 added code to force task stack size 
  18.  *                                  up to reasonable minimum value
  19.  *
  20.  *  10/26/91    JAE                 added support for DJ Delorie's port
  21.  *                                  port of GNU C++ v. 1.39
  22.  *
  23.  *  11/09/91    JAE                 rewrote to use SysQP class for 
  24.  *                                  Sema, Task queues
  25.  *
  26.  *                                  tasks now can have priorities...
  27.  *                                  (be careful of starvation!)
  28.  *
  29.  *  11/11/91    JAE                 added stack basher checking in class
  30.  *                                  Task, called by scheduler
  31.  *
  32.  *  11/13/91    JAE                 moved semas to their own file
  33.  *
  34.  *  11/30/91    jae                 added fDelete flag to Task, so you can
  35.  *                                  dynamically create a new Task on the heap,
  36.  *                                  and then proceed, knowing that the storage
  37.  *                                  will eventually be freed when the task
  38.  *                                  is killed or commits sucicide ...
  39.  *
  40.  *                                  added new queue pZombies, and code in
  41.  *                                  Task::suicide() and scheduer() to support
  42.  *                                  this feature
  43.  *
  44.  *                                  added CHECK_... defines to control
  45.  *                                  run-time checking for fatal errors
  46.  *                                  (calling TaskFatal() if necessary)
  47.  *
  48.  *********************************************************************/
  49.  
  50. #include    "aedef.h"
  51. #include    "task.h"
  52.  
  53. //------------------------------------------------------------
  54. //  local defines to control error checking/reporting:
  55. //  each of these enables to code to check for an error condition
  56. //  and call TaskFatal() with a message if error occurred
  57. //------------------------------------------------------------
  58.  
  59. #define CHECK_SUSPEND       1   //  set to 1 to check for attempt to
  60.                                 //  suspend non-current task
  61.  
  62. #define CHECK_STACK_OVER    1   //  set to 1 to check for stack overflow
  63.  
  64. #define CHECK_STACK_ALLOC   1   //  set to 1 to check for failed attempt
  65.                                 //  to allocate a Task's stack
  66.  
  67. #define CHECK_QUEUE_ALLOC   1   //  set to 1 to check for failure to create
  68.                                 //  the three system task queues
  69.  
  70. //------------------------------------------------------------
  71. //  copyright notice: do not remove this definition!
  72. //------------------------------------------------------------
  73.  
  74. static const char copyright[] = "Task++ v. " TASKPP_VERSION
  75.     " Copyright (c) 1991 J. Alan Eldridge";
  76.  
  77. //------------------------------------------------------------
  78. //  Stack fill value
  79. //------------------------------------------------------------
  80.  
  81. const uchar Task::stkval = 0xAE;
  82.  
  83. //------------------------------------------------------------
  84. //  ********** SCHEDULER **********
  85. //------------------------------------------------------------
  86.  
  87. //------------------------------------------------------------
  88. //  the scheduler's data storage is implemented entirely
  89. //  by these static variables
  90. //------------------------------------------------------------
  91.  
  92. //  static SLList   tasks;      //  list of Tasks
  93.  
  94. //  This variable "tasks" has been changed to a ptr to a
  95. //  list of tasks. This was done at the suggestion of Peter W.
  96. //  at Borland Tech Support. Here's the scenario, since 
  97. //  it is of interest to anyone creating global objects:
  98. //
  99. //  The order of calling global object constructors inside one
  100. //  module is well defined: top to bottom, left to right.
  101. //  The order of calling the constructors in multiple modules
  102. //  is "implementation-defined" and subject to change without
  103. //  notice. That is, I cannot ensure that the constructor for
  104. //  "tasks" is called before the constructors for any global Task
  105. //  instances. The only way I can control when the constructor
  106. //  is called is to allocate the list using operator new.  Thus,
  107. //  the ptr is initialized to 0 (explicitly, since we need to
  108. //  make sure it is done before the startup code calls constructors
  109. //  and zeros uninitialized globals). Then, the code to install
  110. //  a task in the list (sch_AddTask()) checks the value, and
  111. //  creates the list if it doesn't already exist. There is the 
  112. //  slight inefficiency caused by the extra level of indirection,
  113. //  but this is the only reliable way to get the results I need.
  114.  
  115. static SysPriQP *pRunning = 0;  //  ptr to list of running Tasks
  116. static SysQP    *pBlocked = 0;  //  ptr to list of blocked Tasks
  117. static SysQP    *pZombies = 0;  //  ptr to list of zombie Tasks
  118.                                 //  (scheduler will kill them...)
  119.  
  120. static jmp_buf  schEnv;         //  environment in scheduler
  121.  
  122. //------------------------------------------------------------
  123. //  the global CurrTask lets an application function know
  124. //  who's currently executing
  125. //------------------------------------------------------------
  126.  
  127. Task    *CurrTask = 0;
  128.  
  129. //------------------------------------------------------------
  130. //  these 4 calls are the entire interface to the scheduler:
  131. //  SchAddTask(), SchResume(), SchWakeup(), scheduler()
  132. //------------------------------------------------------------
  133.  
  134. //------------------------------------------------------------
  135. //  SchAddTask --  add a new task to the scheduler task list 
  136. //  WARNING: this should only be called by class Task constructor
  137. //------------------------------------------------------------
  138.  
  139. inline void
  140. SchAddTask(Task *t)
  141. {
  142.     if (!pRunning) {
  143.         pRunning = new SysPriQP;
  144.         pBlocked = new SysQP;
  145.         pZombies = new SysQP;
  146. #if CHECK_QUEUE_ALLOC
  147.         if (!(pRunning && pBlocked && pZombies))
  148.             TaskFatal("can't create task queues!");
  149. #endif
  150.     }
  151.     
  152.     pRunning->Add(t);
  153. }
  154.  
  155. //------------------------------------------------------------
  156. //  SchResume  --  return to setjmp() in scheduler()
  157. //------------------------------------------------------------
  158.  
  159. inline void
  160. SchResume(int code = 1)
  161. {
  162.     longjmp(schEnv, code);
  163. }
  164.  
  165. //------------------------------------------------------------
  166. //  SchWakeup() -- wakeup any sleeping or blocked tasks
  167. //------------------------------------------------------------
  168.  
  169. inline void
  170. SchWakeup()
  171. {
  172.     int cnt = pBlocked->Cnt();
  173.     
  174.     while (cnt-- > 0) {
  175.         Task    *pt = GetNxtTask(*pBlocked);
  176.         
  177.         if (pt->maybeWake())
  178.             pRunning->Add(pt);
  179.         else
  180.             pBlocked->Add(pt);
  181.     }
  182. }
  183.  
  184. //------------------------------------------------------------
  185. //  scheduler() --  this is the main scheduler loop
  186. //
  187. //  if the arg is 0, it means somebody wants to shut
  188. //  down all tasks... we do so, then resume the scheduler
  189. //  at the setjmp(), where it will return on its own stack
  190. //
  191. //  otherwise...  every time a task surrenders control it 
  192. //  returns to the setjmp() near the beginning.  we then:
  193. //
  194. //      check for stack overflow
  195. //      if it's not a zombie...
  196. //          place it on the right queue (ready or blocked) 
  197. //
  198. //  then we go into a simple loop:
  199. //
  200. //  while (there are tasks left) loop
  201. //      wakeup anybody who can be awakened
  202. //      get next one off ready queue
  203. //      if nobody ready, continue
  204. //      if it hasn't been initialized, 
  205. //          initialize it
  206. //      else
  207. //          make it return to where it suspended itself
  208. //  end loop
  209. //  
  210. //  returns 1 if forced down by scheduler(0), zero
  211. //  if tasks terminated normally, and optionally calls TaskFatal()
  212. //  if stack basher has been detected
  213. //
  214. //------------------------------------------------------------
  215.  
  216. int
  217. scheduler(int tasking)
  218. {
  219.     if (!tasking) {
  220.         CurrTask = 0;
  221.         SchResume(-1);
  222.     }
  223.  
  224.     int code = setjmp(schEnv);
  225.  
  226.     //  check state of CurrTask and put on a task queue
  227.     
  228.     if (code >= 0 && CurrTask) {
  229. #if CHECK_STACK_OVER
  230.         if (!CurrTask->stackok()) {
  231.             //  die if we blew out the stack
  232.             TaskFatal("stack overflow!");
  233.         } else 
  234. #endif
  235.         {
  236.             //  add to appropriate queue
  237.             if (CurrTask->isReady())
  238.                 pRunning->Add(CurrTask);
  239.             else if (!CurrTask->isZombie())
  240.                 pBlocked->Add(CurrTask);
  241.             else
  242.                 pZombies->Add(CurrTask);
  243.         }
  244.     }
  245.     
  246.     //  clean up any zombies left around
  247.     
  248.     int zcnt = pZombies->Cnt();
  249.     
  250.     while (zcnt-- > 0) {
  251.         Task    *pt = GetNxtTask(*pZombies);
  252.  
  253.         if (pt->shouldDelete())
  254.             delete pt;
  255.     }
  256.         
  257.     //  find next eligible task to run and set it off
  258.     
  259.     while (code >= 0 && (pRunning->Cnt() > 0 || pBlocked->Cnt() > 0)) {
  260.         SchWakeup();
  261.         if (!(CurrTask = GetNxtTask(*pRunning)))
  262.             continue;
  263.         if (!CurrTask->isInited())
  264.             CurrTask->init();
  265.         else
  266.             CurrTask->resume(CurrTask->timeOut() ? -1 : 1);
  267.     }
  268.  
  269.     CurrTask = 0;
  270.     return code < 0;
  271. }
  272.  
  273. //------------------------------------------------------------
  274. //  ********** CLASS TASK MEMBER FUNCTIONS **********
  275. //------------------------------------------------------------
  276.  
  277. //------------------------------------------------------------
  278. //  constructor for a Task: set flags, allocate stack space
  279. //------------------------------------------------------------
  280.  
  281. Task::Task(char *tname, int stk): stklen(stk), tskname(tname)
  282. {
  283.     fInited = 0;
  284.     fReady = 1;
  285.     fTimed = 0;
  286.     fZombie = 0;
  287.     fDelete = 0;
  288.  
  289.     tskpri = 1;
  290.     if (stk < StackMin)
  291.         stk = StackMin;
  292.     stack = new uchar [ stk ];
  293. #if CHECK_STACK_ALLOC
  294.     if (!stack)
  295.         TaskFatal("can't allocate stack for task %s!", tname);
  296. #endif
  297.     memset(stack, stkval, stk);
  298.     SchAddTask(this);
  299. }
  300.  
  301. //------------------------------------------------------------
  302. //  Task::timeOut()  --  return & reset timed out flag
  303. //------------------------------------------------------------
  304.  
  305. int
  306. Task::timeOut()
  307. {
  308.     int timedOut = fTimed;
  309.  
  310.     fTimed = 0;
  311.     return timedOut;
  312. }
  313.  
  314. //------------------------------------------------------------
  315. //  Task::init()    --  initialize a task: switch stacks and
  316. //  call the virtual TaskMain() function, never to return
  317. //------------------------------------------------------------
  318.  
  319. //  this function is used as a wrapper so that the "this"
  320. //  pointer is saved on the stack; that way, if a task returns,
  321. //  it immediately commits suicide
  322.  
  323. static void
  324. callTaskMain(Task *t)
  325. {
  326.     t->TaskMain();  //  call the main function ...
  327.     t->suicide();   //  if it returns, the task wants to die
  328. }
  329.  
  330. void
  331. Task::init()
  332. {
  333.     fInited = 1;    //  we are initialized
  334.     
  335.     //  switch to private stack now
  336.  
  337.     static jmp_buf  stkswitch;
  338.  
  339.     if (!setjmp(stkswitch)) {
  340. #if     defined(__TURBOC__)
  341.         uchar far   *farstk = (uchar far *)stack;
  342.     
  343.         stkswitch[ 0 ].j_ss = FP_SEG(farstk);
  344.         stkswitch[ 0 ].j_sp = FP_OFF(farstk) + stklen;
  345. #elif   defined(__GNUG__)     
  346.         stkswitch[ 0 ].esp = (unsigned int)stack + stklen;
  347. #endif
  348.         longjmp(stkswitch, 1);
  349.     }
  350.  
  351.     //  start task running ...
  352.     callTaskMain(CurrTask); //  ... and never return
  353. }
  354.  
  355. //------------------------------------------------------------
  356. //  Task::stackok() --  have we blown out bottom of stack?
  357. //------------------------------------------------------------
  358.  
  359. int
  360. Task::stackok()
  361. {
  362.     const int   StackChk = 64;
  363.     
  364.     for (int i = 0; i < StackChk; i++)
  365.         if (stack[ i ] != stkval)
  366.             return 0;
  367.  
  368.     return 1;
  369. }
  370.  
  371. //------------------------------------------------------------
  372. //  Task::suicide() --  become a zombie (scheduler will finish off)
  373. //------------------------------------------------------------
  374.  
  375. void
  376. Task::suicide()
  377. {
  378.     fReady = 0;
  379.     fZombie = 1; 
  380.     if (this == CurrTask)       //  if we are the current task, then we
  381.         suspend();              //  aren't on any queues -- just go back
  382.     else {
  383.         pRunning->Del(this);    //  we're on one of these queues, so we
  384.         pBlocked->Del(this);    //  just delete from both (one will succeed)
  385.         pZombies->Add(this);    //  put on Zombie queue (scheduler will kill)
  386.     }
  387. }
  388.  
  389. //------------------------------------------------------------
  390. //  Task::suspend() --  voluntarily give up control to scheduler
  391. //------------------------------------------------------------
  392.  
  393. int
  394. Task::suspend()
  395. {
  396. #if CHECK_SUSPEND
  397.     if (this != CurrTask)
  398.         TaskFatal("suspend task <%s>: not running!", name());
  399. #endif
  400.  
  401.     int n;
  402.  
  403.     if (!(n = setjmp(tskEnv)))
  404.         SchResume();
  405.     return n;
  406. }
  407.  
  408. //------------------------------------------------------------
  409. //  Task::block()   --  block for an event, possibly with
  410. //  a timeout period specified in milliseconds...
  411. //  since the PC clock ticks 18.2 times a second, the granularity
  412. //  of these calls is not too good, but it'll have to do
  413. //------------------------------------------------------------
  414.  
  415. inline clock_t
  416. msecToTicks(clock_t msec)
  417. {
  418.     const clock_t   msecPerTick = 10000 / 182;
  419.  
  420.     return (msec + msecPerTick / 2) / msecPerTick;
  421. }
  422.  
  423. int
  424. Task::block(clock_t msec)
  425. {
  426.     if (msec > -1) {
  427.         wakeUp = clock() + msecToTicks(msec);
  428.         fTimed = 1;
  429.     }
  430.     fReady = 0;
  431.     return suspend() > 0;
  432. }
  433.  
  434. //------------------------------------------------------------
  435. //  Task::unblock() --  return to running queue
  436. //------------------------------------------------------------
  437.  
  438. void
  439. Task::unblock()
  440. {
  441.     fTimed = 0;
  442.     fReady = 1;
  443.     if (pBlocked->Del(this))
  444.         pRunning->Add(this);
  445. }
  446.  
  447. //------------------------------------------------------------
  448. //  Task::maybeWake()   --  check the clock, and if the sleeping
  449. //  task has slept long enough, return TRUE to unblock
  450. //------------------------------------------------------------
  451.  
  452. int
  453. Task::maybeWake()
  454. {
  455.     return fReady = (fTimed && clock() >= wakeUp);
  456. }
  457.  
  458.