home *** CD-ROM | disk | FTP | other *** search
/ Zodiac Super OZ / MEDIADEPOT.ISO / FILES / 13 / COMMIO0B.ZIP / MTASK.PAS < prev    next >
Pascal/Delphi Source File  |  1996-05-06  |  13KB  |  510 lines

  1. UNIT mtask;
  2.  
  3. {MTASK 2.0, a simple multi-tasker unit for Turbo Pascal 5.
  4.  
  5. Written in November, 1988, and donated to the public domain by:
  6.  
  7.    Wayne E. Conrad
  8.    2627 North 51st Ave, #219
  9.    Phoenix, AZ  85035
  10.    BBS: (602) 484-9356, 300/1200/2400, 24 hours/day
  11.  
  12. This unit provides Turbo Pascal 5 with what I call "request driven"
  13. multi-tasking.  Switching from the current task to another task is done
  14. whenever the current task requests a task switch by calling procedure
  15. "switch_task."  No interrupt driven context switching is done, because
  16. it's a hassle.}
  17.  
  18.  
  19. {$F+}  {Most procedures in this unit must be FAR}
  20.  
  21.  
  22. INTERFACE
  23.  
  24.  
  25. {Result codes.  0 is "no error"}
  26.  
  27. CONST
  28.   heap_full       = 1;   {Unable to allocate heap for the task's stack}
  29.   too_many_tasks  = 2;   {Maximum number of tasks are already running}
  30.   invalid_task_id = 3;   {There is no task with that ID number}
  31.  
  32.  
  33. {This is the procedure type for a task.  The parent task can pass any
  34. type of variable to  the child task.}
  35.  
  36. TYPE
  37.   task_proc = PROCEDURE (VAR param);
  38.  
  39.  
  40. {See the IMPLEMENTATION section for descriptions of these procedures and
  41. functions.}
  42.  
  43. PROCEDURE create_task
  44.   (
  45.   task      : task_proc;
  46.   VAR param ;
  47.   stack_size: Word;
  48.   VAR id    : Word;
  49.   VAR result: Word
  50.   );
  51. PROCEDURE terminate_task (id: Word; VAR result: Word);
  52. PROCEDURE switch_task;
  53. FUNCTION current_task_id: Word;
  54. FUNCTION number_of_tasks: Word;
  55.  
  56. {The maximum number of tasks.  Modify to suit your needs.}
  57. CONST
  58.   max_tasks = 32;
  59.  
  60. IMPLEMENTATION
  61.  
  62.  
  63.  
  64. {This record contains all the information about a task, as follows:
  65.  
  66.   stack_ptr:   Saved stack segment (ss) and stack pointer (sp) registers
  67.  
  68.   stack_org:   If the stack is stored on the heap, this is the address of
  69.                the beginning of the block of memory allocated for the stack.
  70.  
  71.   stack_bytes: Size of stack on the heap, or 0 if the stack is not on the
  72.                heap.  If the stack is not on the heap, then this field is 0.
  73.  
  74.   bp:          Saved value of base pointer (BP) register.
  75.  
  76.   id:          The id number of the task
  77.  
  78. Note that DS (Data Segment register) is not stored.  We can get away with
  79. this by assuming that all tasks will use the same data segment.}
  80.  
  81. TYPE
  82.   task_rec =
  83.     RECORD
  84.     stack_ptr  : Pointer;
  85.     stack_org  : Pointer;
  86.     stack_bytes: Word;
  87.     bp         : Word;
  88.     id         : Word;
  89.     END;
  90.  
  91.  
  92. {The number of tasks in the system}
  93.  
  94. VAR
  95.   ntasks: Word;
  96.  
  97.  
  98. {Information for each task.}
  99.  
  100. VAR
  101.   task_info: ARRAY [1..max_tasks] OF task_rec;
  102.  
  103.  
  104. {The last task ID assigned.  If we haven't rolled the id's over, then
  105. this allows us to assign task ID's without checking to see what id's have
  106. been assigned.}
  107.  
  108. VAR
  109.   last_id    : Word;
  110.   id_rollover: Boolean;
  111.  
  112.  
  113. {This is the task number of the currently executing task}
  114.  
  115. VAR
  116.   current_task: Word;
  117.  
  118.  
  119. {This is the record type of the initial contents of the stack when a task
  120. is created.  When the task is first switched to, it will be from within
  121. the switch_task, terminate_task, or terminate_current_task procedure.  At
  122. the end of switch_task, BP will be popped, then a far return will be
  123. done.  The far return will transfer to the beginning of task.  The task
  124. can access the parameter "task_param," which is a pointer to whatever
  125. data structure that the creator of this task wanted to pass to the new
  126. task.  When the task finally exits, a far return to "end_task" will be
  127. done.  The exception is the main task, which ends the program completely
  128. if it exits.}
  129.  
  130. TYPE
  131.   initial_stack_rec_ptr = ^initial_stack_rec;
  132.   initial_stack_rec =
  133.     RECORD
  134.     bp        : Word;
  135.     task_addr : task_proc;
  136.     end_task  : Pointer;
  137.     task_param: Pointer;
  138.     END;
  139.  
  140.  
  141. {Given a task ID, return the task number, or 0 if there is no task with
  142. that ID.}
  143.  
  144. FUNCTION find_task (target_id: Word): Word;
  145. VAR
  146.   n: Word;
  147. BEGIN
  148.   n := 1;
  149.   WHILE (n <= ntasks) AND (task_info [n].id <> target_id) DO
  150.     Inc (n);
  151.   IF (n > ntasks) THEN
  152.     n := 0;
  153.   find_task := n
  154. END;
  155.  
  156.  
  157. {Remove a task's information from the task info array, and decrement the
  158. number of tasks.}
  159.  
  160. PROCEDURE delete_task_info (task_num: Word);
  161. VAR
  162.   i: Word;
  163. BEGIN
  164.   FOR i := task_num TO ntasks - 1 DO
  165.     task_info [i] := task_info [i + 1];
  166.   Dec (ntasks)
  167. END;
  168.  
  169.  
  170. {Terminate the current task.  If the current task is the only task, then
  171. the program is halted.  If the current task's stack was allocated from
  172. the heap, it is freed.}
  173.  
  174. PROCEDURE terminate_current_task;
  175.  
  176.  
  177. {These are defined as constants to force them into the data segment.
  178. They can't be local, because local variables are stored on the stack and
  179. we're going to switch to a different task (and therefore to a different
  180. stack) before we're done with these variables.}
  181.  
  182. CONST
  183.   old_stack_org  : Pointer = NIL;
  184.   old_stack_bytes: Word = 0;
  185.  
  186.  
  187. VAR
  188.   task_num : Word;
  189.   new_stack: Pointer;
  190.   new_bp   : Word;
  191.  
  192.  
  193. BEGIN {terminate_current_task}
  194.  
  195.   {If we're the last task left, then exit to DOS}
  196.  
  197.   IF ntasks <= 1 THEN
  198.     Halt;
  199.  
  200.   {Remember where the task's stack is so that we can free it up if it's
  201.   on the heap.  We can't free it now, because we're still using it!}
  202.  
  203.   WITH task_info [current_task] DO
  204.     BEGIN
  205.     old_stack_org   := stack_org;
  206.     old_stack_bytes := stack_bytes
  207.     END;
  208.  
  209.   {Remove the task's information from the task info array}
  210.  
  211.   delete_task_info (current_task);
  212.   IF current_task > ntasks THEN
  213.     current_task := 1;
  214.  
  215.   {Switch to the next task.  The stack_ptr and bp are transfered into
  216.   local variables because it's much easier to access simple variables in
  217.   INLINE code than it is to access array variables.}
  218.  
  219.   WITH task_info [current_task] DO
  220.     BEGIN
  221.     new_stack := stack_ptr;
  222.     new_bp    := bp
  223.     END;
  224.   INLINE
  225.     (
  226.     $8b/$86/>new_stack+0/     {MOV  AX,[BP].NEW_STACK+0}
  227.     $8b/$96/>new_stack+2/     {MOV  DX,[BP].NEW_STACK+2}
  228.     $8b/$ae/>new_bp/          {MOV  BP,[BP].NEW_BP}
  229.     $fa/                      {CLI}
  230.     $8e/$d2/                  {MOV  SS,DX}
  231.     $8b/$e0/                  {MOV  SP,AX}
  232.     $fb                       {STI}
  233.     );
  234.  
  235.   {If the task we just got rid of had its heap on the stack, then release
  236.   that memory back to the free pool.}
  237.  
  238.   IF old_stack_bytes > 0 THEN
  239.     FreeMem (old_stack_org, old_stack_bytes)
  240.  
  241. END;
  242.  
  243.  
  244. {Terminate a task.  If task_id is 0, then the current task is deleted.
  245. Possible result codes are:
  246.  
  247.   0                   No error
  248.   invalid_task_id     There is no task with that ID number}
  249.  
  250. PROCEDURE terminate_task (id: Word; VAR result: Word);
  251.  
  252.  
  253.   {Delete a task.  Do not use to delete the current task!}
  254.  
  255.   PROCEDURE delete_task (task_num: Word);
  256.   BEGIN
  257.     WITH task_info [task_num] DO
  258.       IF stack_bytes > 0 THEN
  259.         FreeMem (stack_org, stack_bytes);
  260.     delete_task_info (task_num);
  261.     IF current_task > task_num THEN
  262.       Dec (current_task)
  263.   END;
  264.  
  265.  
  266. VAR
  267.   task_num: Word;
  268.  
  269. BEGIN {terminate_task}
  270.   result := 0;
  271.   IF id = 0 THEN
  272.     terminate_current_task
  273.   ELSE
  274.     BEGIN
  275.     task_num := find_task (id);
  276.     IF task_num = 0 THEN
  277.       result := invalid_task_id
  278.     ELSE
  279.       IF task_num = current_task THEN
  280.         terminate_current_task
  281.       ELSE
  282.         delete_task (task_num)
  283.     END
  284. END;
  285.  
  286.  
  287. {Create a new task and pass parameter "param" to it.  Stack space for the
  288. task is allocated from the heap, and the stack is initialized so that
  289. procedure "new_task" will be executed with parameter "param".  Result
  290. codes are:
  291.  
  292.   0                  No error occured
  293.   heap_full          Unable to allocate heap for the task's stack
  294.   too_many_tasks     Maximum number of tasks are already running
  295.  
  296. If an error occurs, then id is not set.  Otherwise, id is the task id of
  297. the newly created task.}
  298.  
  299. PROCEDURE create_task
  300.   (
  301.   task      : task_proc;
  302.   VAR param ;
  303.   stack_size: Word;
  304.   VAR id    : Word;
  305.   VAR result: Word
  306.   );
  307.  
  308.  
  309. {This is the task number of the task we're creating}
  310.  
  311. VAR
  312.   task_num: Word;
  313.  
  314.  
  315.   {Allocate stack space for the task.  The minimum allowable requested
  316.   stack size is 512 bytes.  For some reason, the stack-check procedure in
  317.   Turbo's run-time library has that limit hard-coded into it.
  318.  
  319.   stack_org is set to the address of the beginning of the block of memory
  320.   allocated for the stack.
  321.  
  322.   stack_bytes is set to the size of the block of memory allocated for the
  323.   stack.}
  324.  
  325.   PROCEDURE create_stack;
  326.   BEGIN
  327.     IF stack_size < 512 THEN
  328.       stack_size := 512;
  329.     IF stack_size > MaxAvail THEN
  330.       result := heap_full
  331.     ELSE
  332.       WITH task_info [task_num] DO
  333.         BEGIN
  334.         GetMem (stack_org, stack_size);
  335.         stack_bytes := stack_size
  336.         END
  337.   END;
  338.  
  339.  
  340.   {Initialize the stack and the stack pointer.  The structure
  341.   "initial_stack_rec" is placed at the top of the stack area, with the
  342.   stack pointer pointing to its lowest element.  See the comments for
  343.   initial_stack_rec for what the stuff in initial_stack_rec actually
  344.   does.}
  345.  
  346.   PROCEDURE init_stack;
  347.   VAR
  348.     stack_ofs: Word;
  349.   BEGIN
  350.     WITH task_info [task_num] DO
  351.       BEGIN
  352.       stack_ofs := Ofs (stack_org^) + stack_bytes - Sizeof (initial_stack_rec);
  353.       stack_ptr := Ptr (Seg (stack_org^), stack_ofs);
  354.       bp := Ofs (stack_ptr^);
  355.       WITH initial_stack_rec_ptr (stack_ptr)^ DO
  356.         BEGIN
  357.         task_param := @param;
  358.         task_addr  := task;
  359.         end_task   := @terminate_current_task;
  360.         bp         := 0
  361.         END
  362.       END
  363.   END;
  364.  
  365.  
  366.   {Find an unused task id and assign it to the new task}
  367.  
  368.   PROCEDURE assign_task_id;
  369.  
  370.  
  371.     {Increment "last_id" to (hopefully) turn it into the task_id we're
  372.     going to assign.  If it rolls over, set it to 2 (task 1 will always
  373.     exist, since it's the root task) and remember that we've rolled
  374.     over.}
  375.  
  376.     PROCEDURE increment_last_id;
  377.     BEGIN
  378.       IF last_id = 65535 THEN
  379.         BEGIN
  380.         last_id := 2;
  381.         id_rollover := True
  382.         END
  383.       ELSE
  384.         Inc (last_id)
  385.     END;
  386.  
  387.  
  388.   BEGIN {assign_task_id}
  389.     increment_last_id;
  390.     IF id_rollover THEN
  391.       WHILE (find_task (last_id) <> 0) DO
  392.         increment_last_id;
  393.     id := last_id;
  394.     task_info [task_num].id := id
  395.   END;
  396.  
  397.  
  398. BEGIN {create_task}
  399.   result := 0;
  400.   IF ntasks >= max_tasks THEN
  401.     result := too_many_tasks
  402.   ELSE
  403.     BEGIN
  404.     task_num := Succ (ntasks);
  405.     create_stack;
  406.     IF result = 0 THEN
  407.       BEGIN
  408.       init_stack;
  409.       assign_task_id;
  410.       Inc (ntasks)
  411.       END
  412.     END
  413. END;
  414.  
  415.  
  416. {Switch to the next task}
  417.  
  418. PROCEDURE switch_task;
  419.  
  420. VAR
  421.   new_stack: Pointer;
  422.   old_bp   : Word;
  423.   new_bp   : Word;
  424.  
  425. BEGIN
  426.  
  427.   {Only switch if there are other tasks to switch to}
  428.  
  429.   IF ntasks > 1 THEN
  430.     BEGIN
  431.  
  432.     {Save the current value of SS, SP, and BP for this task}
  433.  
  434.     INLINE
  435.       (
  436.       $89/$ae/>old_bp           {MOV  OLD_BP,BP}
  437.       );
  438.     WITH task_info [current_task] DO
  439.       BEGIN
  440.       stack_ptr := Ptr (Sseg, Sptr);
  441.       bp        := old_bp
  442.       END;
  443.  
  444.     {Switch to the next task.  The bit with new_stack and new_bp are
  445.     because it's easier to write INLINE code to access a simple variable
  446.     than it is to access a record of an array.}
  447.  
  448.     IF current_task >= ntasks THEN
  449.       current_task := 1
  450.     ELSE
  451.       Inc (current_task);
  452.     WITH task_info [current_task] DO
  453.       BEGIN
  454.       new_stack := stack_ptr;
  455.       new_bp    := bp
  456.       END;
  457.     INLINE
  458.       (
  459.       $8b/$86/>new_stack+0/     {MOV  AX,[BP].NEW_STACK+0}
  460.       $8b/$96/>new_stack+2/     {MOV  DX,[BP].NEW_STACK+2}
  461.       $8b/$ae/>new_bp/          {MOV  BP,[BP].NEW_BP}
  462.       $Fa/                      {CLI}
  463.       $8e/$d2/                  {MOV  SS,DX}
  464.       $8b/$e0/                  {MOV  SP,AX}
  465.       $fb                       {STI}
  466.       )
  467.     END
  468. END;
  469.  
  470.  
  471. {Return the id number of the currently executing task}
  472.  
  473. FUNCTION current_task_id: Word;
  474. BEGIN
  475.   current_task_id := task_info [current_task].id
  476. END;
  477.  
  478.  
  479. {Return the number of tasks}
  480.  
  481. FUNCTION number_of_tasks: Word;
  482. BEGIN
  483.   number_of_tasks := ntasks
  484. END;
  485.  
  486.  
  487. {Initialize this unit.  The task list is initialized to contain the
  488. current task, whose task id is 1.}
  489.  
  490. PROCEDURE init_mtask;
  491. VAR
  492.   id: Word;
  493. BEGIN
  494.   ntasks := 1;
  495.   current_task := 1;
  496.   WITH task_info [current_task] DO
  497.     BEGIN
  498.     stack_org   := NIL;
  499.     stack_bytes := 0;
  500.     id          := 1
  501.     END;
  502.   last_id := 1;
  503.   id_rollover := False
  504. END;
  505.  
  506.  
  507. BEGIN {mtask}
  508.   init_mtask
  509. END.
  510.