*/
Know a good article or link that we're missing? Submit it!
*/

View \MTASK.PAS

MTASK 2.0

Submitted By: WEBMASTER
Rating: starstarstarstar (Rate It)


UNIT mtask;


{MTASK 2.0, a simple multi-tasker unit for Turbo Pascal 5.

Written in November, 1988, and donated to the public domain by:

   Wayne E. Conrad
   2627 North 51st Ave, #219
   Phoenix, AZ  85035
   BBS: (602) 484-9356, 300/1200/2400, 24 hours/day

This unit provides Turbo Pascal 5 with what I call "request driven"
multi-tasking.  Switching from the current task to another task is done
whenever the current task requests a task switch by calling procedure
"switch_task."  No interrupt driven context switching is done, because
it's a hassle.

See accompanying files for documentation and examples.}



{$F+}  {Most procedures in this unit must be FAR}


INTERFACE


{Result codes.  0 is "no error"}

CONST
  heap_full       = 1;   {Unable to allocate heap for the task's stack}
  too_many_tasks  = 2;   {Maximum number of tasks are already running}
  invalid_task_id = 3;   {There is no task with that ID number}


{This is the procedure type for a task.  The parent task can pass any
type of variable to  the child task.}


TYPE
  task_proc = PROCEDURE (VAR param);


{See the IMPLEMENTATION section for descriptions of these procedures and
functions.}


PROCEDURE create_task
  (
  task      : task_proc;
  VAR param ;
  stack_size: Word;
  VAR id    : Word;
  VAR result: Word
  );
PROCEDURE terminate_task (id: Word; VAR result: Word);
PROCEDURE switch_task;
FUNCTION current_task_id: Word;
FUNCTION number_of_tasks: Word;


IMPLEMENTATION


{The maximum number of tasks.  Modify to suit your needs.}

CONST
  max_tasks = 16;


{This record contains all the information about a task, as follows:

  stack_ptr:   Saved stack segment (ss) and stack pointer (sp) registers

  stack_org:   If the stack is stored on the heap, this is the address of
               the beginning of the block of memory allocated for the stack.

  stack_bytes: Size of stack on the heap, or 0 if the stack is not on the
               heap.  If the stack is not on the heap, then this field is 0.

  bp:          Saved value of base pointer (BP) register.

  id:          The id number of the task

Note that DS (Data Segment register) is not stored.  We can get away with
this by assuming that all tasks will use the same data segment.}


TYPE
  task_rec =
    RECORD
    stack_ptr  : Pointer;
    stack_org  : Pointer;
    stack_bytes: Word;
    bp         : Word;
    id         : Word;
    END;


{The number of tasks in the system}

VAR
  ntasks: Word;


{Information for each task.}

VAR
  task_info: ARRAY [1..max_tasks] OF task_rec;


{The last task ID assigned.  If we haven't rolled the id's over, then
this allows us to assign task ID's without checking to see what id's have
been assigned.}


VAR
  last_id    : Word;
  id_rollover: Boolean;


{This is the task number of the currently executing task}

VAR
  current_task: Word;


{This is the record type of the initial contents of the stack when a task
is created.  When the task is first switched to, it will be from within
the switch_task, terminate_task, or terminate_current_task procedure.  At
the end of switch_task, BP will be popped, then a far return will be
done.  The far return will transfer to the beginning of task.  The task
can access the parameter "task_param," which is a pointer to whatever
data structure that the creator of this task wanted to pass to the new
task.  When the task finally exits, a far return to "end_task" will be
done.  The exception is the main task, which ends the program completely
if it exits.}


TYPE
  initial_stack_rec_ptr = ^initial_stack_rec;
  initial_stack_rec =
    RECORD
    bp        : Word;
    task_addr : task_proc;
    end_task  : Pointer;
    task_param: Pointer;
    END;


{Given a task ID, return the task number, or 0 if there is no task with
that ID.}


FUNCTION find_task (target_id: Word): Word;
VAR
  n: Word;
BEGIN
  n := 1;
  WHILE (n <= ntasks) AND (task_info [n].id <> target_id) DO
    Inc (n);
  IF (n > ntasks) THEN
    n := 0;
  find_task := n
END;


{Remove a task's information from the task info array, and decrement the
number of tasks.}


PROCEDURE delete_task_info (task_num: Word);
VAR
  i: Word;
BEGIN
  FOR i := task_num TO ntasks - 1 DO
    task_info [i] := task_info [i + 1];
  Dec (ntasks)
END;


{Terminate the current task.  If the current task is the only task, then
the program is halted.  If the current task's stack was allocated from
the heap, it is freed.}


PROCEDURE terminate_current_task;


{These are defined as constants to force them into the data segment.
They can't be local, because local variables are stored on the stack and
we're going to switch to a different task (and therefore to a different
stack) before we're done with these variables.}


CONST
  old_stack_org  : Pointer = NIL;
  old_stack_bytes: Word = 0;


VAR
  task_num : Word;
  new_stack: Pointer;
  new_bp   : Word;


BEGIN {terminate_current_task}

  {If we're the last task left, then exit to DOS}

  IF ntasks <= 1 THEN
    Halt;

  {Remember where the task's stack is so that we can free it up if it's
  on the heap.  We can't free it now, because we're still using it!}


  WITH task_info [current_task] DO
    BEGIN
    old_stack_org   := stack_org;
    old_stack_bytes := stack_bytes
    END;

  {Remove the task's information from the task info array}

  delete_task_info (current_task);
  IF current_task > ntasks THEN
    current_task := 1;

  {Switch to the next task.  The stack_ptr and bp are transfered into
  local variables because it's much easier to access simple variables in
  INLINE code than it is to access array variables.}


  WITH task_info [current_task] DO
    BEGIN
    new_stack := stack_ptr;
    new_bp    := bp
    END;
  INLINE
    (
    $8b/$86/>new_stack+0/     {MOV  AX,[BP].NEW_STACK+0}
    $8b/$96/>new_stack+2/     {MOV  DX,[BP].NEW_STACK+2}
    $8b/$ae/>new_bp/          {MOV  BP,[BP].NEW_BP}
    $fa/                      {CLI}
    $8e/$d2/                  {MOV  SS,DX}
    $8b/$e0/                  {MOV  SP,AX}
    $fb                       {STI}
    );

  {If the task we just got rid of had its heap on the stack, then release
  that memory back to the free pool.}


  IF old_stack_bytes > 0 THEN
    FreeMem (old_stack_org, old_stack_bytes)

END;


{Terminate a task.  If task_id is 0, then the current task is deleted.
Possible result codes are:

  0                   No error
  invalid_task_id     There is no task with that ID number}


PROCEDURE terminate_task (id: Word; VAR result: Word);


  {Delete a task.  Do not use to delete the current task!}

  PROCEDURE delete_task (task_num: Word);
  BEGIN
    WITH task_info [task_num] DO
      IF stack_bytes > 0 THEN
        FreeMem (stack_org, stack_bytes);
    delete_task_info (task_num);
    IF current_task > task_num THEN
      Dec (current_task)
  END;


VAR
  task_num: Word;

BEGIN {terminate_task}
  result := 0;
  IF id = 0 THEN
    terminate_current_task
  ELSE
    BEGIN
    task_num := find_task (id);
    IF task_num = 0 THEN
      result := invalid_task_id
    ELSE
      IF task_num = current_task THEN
        terminate_current_task
      ELSE
        delete_task (task_num)
    END
END;


{Create a new task and pass parameter "param" to it.  Stack space for the
task is allocated from the heap, and the stack is initialized so that
procedure "new_task" will be executed with parameter "param".  Result
codes are:

  0                  No error occured
  heap_full          Unable to allocate heap for the task's stack
  too_many_tasks     Maximum number of tasks are already running

If an error occurs, then id is not set.  Otherwise, id is the task id of
the newly created task.}


PROCEDURE create_task
  (
  task      : task_proc;
  VAR param ;
  stack_size: Word;
  VAR id    : Word;
  VAR result: Word
  );


{This is the task number of the task we're creating}

VAR
  task_num: Word;


  {Allocate stack space for the task.  The minimum allowable requested
  stack size is 512 bytes.  For some reason, the stack-check procedure in
  Turbo's run-time library has that limit hard-coded into it.

  stack_org is set to the address of the beginning of the block of memory
  allocated for the stack.

  stack_bytes is set to the size of the block of memory allocated for the
  stack.}


  PROCEDURE create_stack;
  BEGIN
    IF stack_size < 512 THEN
      stack_size := 512;
    IF stack_size > MaxAvail THEN
      result := heap_full
    ELSE
      WITH task_info [task_num] DO
        BEGIN
        GetMem (stack_org, stack_size);
        stack_bytes := stack_size
        END
  END;


  {Initialize the stack and the stack pointer.  The structure
  "initial_stack_rec" is placed at the top of the stack area, with the
  stack pointer pointing to its lowest element.  See the comments for
  initial_stack_rec for what the stuff in initial_stack_rec actually
  does.}


  PROCEDURE init_stack;
  VAR
    stack_ofs: Word;
  BEGIN
    WITH task_info [task_num] DO
      BEGIN
      stack_ofs := Ofs (stack_org^) + stack_bytes - Sizeof (initial_stack_rec);
      stack_ptr := Ptr (Seg (stack_org^), stack_ofs);
      bp := Ofs (stack_ptr^);
      WITH initial_stack_rec_ptr (stack_ptr)^ DO
        BEGIN
        task_param := @param;
        task_addr  := task;
        end_task   := @terminate_current_task;
        bp         := 0
        END
      END
  END;


  {Find an unused task id and assign it to the new task}

  PROCEDURE assign_task_id;


    {Increment "last_id" to (hopefully) turn it into the task_id we're
    going to assign.  If it rolls over, set it to 2 (task 1 will always
    exist, since it's the root task) and remember that we've rolled
    over.}


    PROCEDURE increment_last_id;
    BEGIN
      IF last_id = 65535 THEN
        BEGIN
        last_id := 2;
        id_rollover := True
        END
      ELSE
        Inc (last_id)
    END;


  BEGIN {assign_task_id}
    increment_last_id;
    IF id_rollover THEN
      WHILE (find_task (last_id) <> 0) DO
        increment_last_id;
    id := last_id;
    task_info [task_num].id := id
  END;


BEGIN {create_task}
  result := 0;
  IF ntasks >= max_tasks THEN
    result := too_many_tasks
  ELSE
    BEGIN
    task_num := Succ (ntasks);
    create_stack;
    IF result = 0 THEN
      BEGIN
      init_stack;
      assign_task_id;
      Inc (ntasks)
      END
    END
END;


{Switch to the next task}

PROCEDURE switch_task;

VAR
  new_stack: Pointer;
  old_bp   : Word;
  new_bp   : Word;

BEGIN

  {Only switch if there are other tasks to switch to}

  IF ntasks > 1 THEN
    BEGIN

    {Save the current value of SS, SP, and BP for this task}

    INLINE
      (
      $89/$ae/>old_bp           {MOV  OLD_BP,BP}
      );
    WITH task_info [current_task] DO
      BEGIN
      stack_ptr := Ptr (Sseg, Sptr);
      bp        := old_bp
      END;

    {Switch to the next task.  The bit with new_stack and new_bp are
    because it's easier to write INLINE code to access a simple variable
    than it is to access a record of an array.}


    IF current_task >= ntasks THEN
      current_task := 1
    ELSE
      Inc (current_task);
    WITH task_info [current_task] DO
      BEGIN
      new_stack := stack_ptr;
      new_bp    := bp
      END;
    INLINE
      (
      $8b/$86/>new_stack+0/     {MOV  AX,[BP].NEW_STACK+0}
      $8b/$96/>new_stack+2/     {MOV  DX,[BP].NEW_STACK+2}
      $8b/$ae/>new_bp/          {MOV  BP,[BP].NEW_BP}
      $Fa/                      {CLI}
      $8e/$d2/                  {MOV  SS,DX}
      $8b/$e0/                  {MOV  SP,AX}
      $fb                       {STI}
      )
    END
END;


{Return the id number of the currently executing task}

FUNCTION current_task_id: Word;
BEGIN
  current_task_id := task_info [current_task].id
END;


{Return the number of tasks}

FUNCTION number_of_tasks: Word;
BEGIN
  number_of_tasks := ntasks
END;


{Initialize this unit.  The task list is initialized to contain the
current task, whose task id is 1.}


PROCEDURE init_mtask;
VAR
  id: Word;
BEGIN
  ntasks := 1;
  current_task := 1;
  WITH task_info [current_task] DO
    BEGIN
    stack_org   := NIL;
    stack_bytes := 0;
    id          := 1
    END;
  last_id := 1;
  id_rollover := False
END;


BEGIN {mtask}
  init_mtask
END.

corner
© 1996-2008 CommunityHeaven LLC. All rights reserved. Reproduction in whole or in part, in any form or medium without express written permission is prohibited.
Violators of this policy may be subject to legal action. Please read our Terms Of Use and Privacy Statement for more information.
North American business development: Nicolai Wadstrom. Publisher: Lars Hagelin.