home *** CD-ROM | disk | FTP | other *** search
- .dhMTASK 2.0 by Wayne Conrad
- .np Documentation written using Multi-Edit 4.01Pd
- .rm 72
- .df .ce -.pa-
- .tm6
-
-
-
-
-
-
- .ceMTASK
-
-
- MTASK is a unit for Turbo Pascal 5.5, to allow a Turbo Pascal program
- to exhibit simple multi-tasking. MTASK gives your program a
- non-preemptive, request driven multi-tasking capability. I will
- explain what I mean by that later.
-
- MTASK 2.0 was written and donated to the public domain by Wayne E.
- Conrad (me) in March of 1990. I may be contact via my BBS,
-
- Pascalaholics Anonymous
- (602) 484-9356
- 300/1200/2400 bps
- 24 hours/day
-
- or by mail at my home:
-
- 10 E Bell Road #1001
- Phoenix, AZ 85022
-
- I am interested in any modifications, bug reports, or comments you
- have.
-
- If you modify this package, please keep my name and the name of any
- other programmers who've worked on it intact. Give credit to yourself,
- too! Please distribute the complete package, with documentation and
- demonstration programs included.
-
-
- 1.1 INTRODUCTION
-
-
- MTASK allows your Turbo Pascal program to do simple multi-tasking. I
- call MTASK's brand of multi-tasking "non-preemptive, request driven."
-
- Preemptive means that the switch from one task to another can happen at
- almost any time. Most preemptive systems have an interrupt driver
- hooked to a hardware timer, which causes a task switch every time the
- hardware timer goes off. The advantage of this kind of multi-tasking
- is that your programs don't have to be written with multi-tasking in
- mind, and don't even have to know that its taking place. Also, no
- program can hog the system for long, because the interrupt driver
- switches from one program to another fairly often. Desqview and
- Double-DOS, and OS/2 are operating environments that do preemptive,
- interrupt-driven multi-tasking. The disadvantage of this kind of
- multi-tasking is that it can be complex to write and difficult in the
- extreme to debug. These difficulties are compounded in an MS-DOS
- environment because MS-DOS was not meant to be used in a multi-tasking
- environment.
-
- On the other hand, non-preemptive multi-tasking only switches tasks at
- certain, well defined times. There is no interrupt driver that forces
- task switches. In the original Macintosh operating system, for example,
- task switches only occured when a task called the operating system. The
- advantage of non-preemptive multi-tasking is that it is much simpler to
- write and debug than preemptive multi-tasking, because the system has
- total control of when task-switches occur. The disadvantage to this
- form of multi-tasking is that a task must request a task switch often if
- the other tasks are to receive their chance to execute. If a task does
- not request a task switch for a long time, the other tasks will appear
- to pause. What's worse, if a task crashes, it won't be able to call the
- operating system to let the other tasks execute, so they'll all be hung
- too.
-
- MTASK implements a very simple method of non-preemptive multi-tasking
- that I call "request driven." Request driven means that task switches
- occur only when the current task calls MTASK and requests a switch.
- (The sole exception is that a task switch occurs when the current task
- terminates itself). This is about the simplest form of multi-tasking
- that I can envision. It is so simple that the entire MTASK unit
- compiles to only about 1400 bytes with stack checking and range
- checking turned off, or less if you don't use all of its procedures.
- This simplicity also made MTASK easy to write and debug. MTASK was
- written in one day!
-
-
-
- 1.2 WHAT ARE MTASK'S LIMITS?
-
-
- MTASK allows your program to set up multiple tasks within itself.
- These tasks will execute concurrently. However, it does not effect
- anything outside of your program. It does not allow you to run
- multiple programs, multiple copies of COMMAND.COM, or anything else
- like that. It simply allows your program to do several things
- concurrently without stumbling over itself.
-
- As far as DOS is concerned, your program using MTASK is still just a
- simple program. All of the gymnastics to keep track of multiple tasks
- are done by MTASK, withing your program, without the knowledge or
- consent of DOS or anything else outside of your program. Because MTASK
- is so simple, it will coexist fine with any "real" multi-tasking DOS
- you have set up, such as DesqView or Double-DOS. Whenever the DOS
- gives your program some time, your program will dole out that time to
- its tasks.
-
- Your program must continue to execute for its tasks to execute. If any
- task in your program exits to DOS for any reason, including a run-time
- error, all tasks stop executing. If one of your tasks shells out by
- using Turbo's Exec function, then the other tasks in your program are
- suspended until control returns from the Exec function to your program.
-
- MTASK must not be made into an overlay. Any of the tasks it controls
- may be overlays, although that may be unwise. You could end up loading
- an overlay from disk during each task switch!
-
-
- 2.1 SUMMARY OF PROCEDURES AND FUNCTIONS
-
-
- To use MTASK, include it in your program's USES statements. MTASK will
- initialize itself automatically, making your main program task #1.
- Your program can then use the following procedures and functions to
- create and control tasks:
-
-
- create_task Create another task
-
- terminate_task Destroy a task
-
- switch_task Switch to another task
-
- current_task_id Return the task ID of the current task
-
- number_of_tasks Return the current number of tasks
-
-
- 2.1.1 PROCEDURE CREATE_TASK
-
-
- PROCEDURE create_task
- (
- task : task_proc;
- VAR param ;
- stack_size: Word;
- VAR id : Word;
- VAR result: Word
- );
-
-
- TASK is the procedure to make into a task. It must match type
- task_proc, having a single variable as its parameter.
-
- PARAM is the parameter to pass to new_task. It may be a variable
- of any type, so long its what the task expects. For example, if
- you pass a Word and the task expects a LongInt, the task will get
- invalid data.
-
- STACK_SIZE is the size of the new task's stack. A stack will be
- allocated from the heap. The minimum stack size is 512 bytes, but
- most tasks will need more.
-
- ID is set to the task ID of the newly created task. If the task
- is not created because of an error, then id is not set.
-
- RESULT is the result code, which is set to one of these values:
-
- 0 No error, task created ok
-
- heap_full Unable to allocate heap for the task's
- stack
-
- too_many_tasks Maximum number of tasks are already
- running
-
-
- The new task is created and added to the end of the task list. The new
- task will be executed when the task before it calls switch_task.
-
-
- 2.1.2 PROCEDURE TERMINATE_TASK
-
-
- PROCEDURE terminate_task (id: Word; VAR result: Word);
-
-
- ID is the task id of the task you want to terminate. If ID = 0,
- then the current task will be terminated.
-
- RESULT is the result code, which is set to one of these values.
-
- 0 No error, task deleted ok
-
- invalid_task_id There is no task with that ID number
-
-
- The designated task will be removed from the task list. If its stack
- was allocated from the heap, it is returned to the heap.
-
- If the terminated task is the current task and there is another task in
- the task list, a task switch occurs. On the other hand, if the
- terminated task is the current task and there are no other tasks in the
- task list, then the program exits to DOS.
-
- A task may terminate itself by returning from its main procedure. For
- example, when this task is executed, it will immediately display a
- message and then terminate itself.
-
-
- PROCEDURE task (VAR param);
- BEGIN
- Writeln ('We just started, but already we're terminating')
- END;
-
-
- 2.1.3 PROCEDURE switch_task
-
-
- PROCEDURE switch_task;
-
-
- This procedure causes an immediate switch to the next task in the task
- list. The task list is always scanned as a circular list. For
- example, if there are three tasks in the list -- task 1, task 2, and
- task 3 -- then they will be executed in this order:
-
-
- 1, 2, 3, 1, 2, 3, 1, 2, 3 . . .
-
-
- If the current task is the only task, then no task switch occurs.
-
- The stack pointer is switched to its position in the new task's stack.
- If the new task has just been created, then its main procedure will be
- executed from the beginning. On the other hand, if the new task had
- put itself to sleep by asking for a task switch, then control will
- return to the point where it called switch_task.
-
-
-
- 2.1.4 FUNCTION CURRENT_TASK_ID
-
-
- FUNCTION current_task_id: Word;
-
-
- This function returns the task ID number of the currently executing
- task. When calling an MTASK procedure to do something to a task, the
- task ID number is always used to identify the task.
-
- A task is assigned its ID number when it is created. A task's ID
- number belongs to it as long as that task exists, and will not be
- changed or reassigned until the task terminates. Even after the task
- terminates, Mtask will avoid re-assigning its ID for as long as
- possible. Since there are 65535 possible task ID's, this could be a
- very long time indeed.
-
-
-
- 2.1.5 FUNCTION NUMBER_OF_TASKS
-
-
- FUNCTION number_of_tasks: Word;
-
-
- This function returns the number of tasks in the task list. There will
- always be at least one task.
-
-
- 3.1 TRICKS AND TRAPS
-
-
- This section focuses on some of the tricks and traps of programming in
- this multi-tasking environment. Like all multi-tasking environments,
- strange things can happen. You'll learn how to watch for problems with
- shared data, and crunched parameters.
-
- I will only give a few examples of the problems that can occur in
- multi-tasking environments. There are other problems that can occur
- when using MTASK, although the problems are less numerous and simpler
- to solve than when using a preemptive multi-tasking system. This
- section should help you to begin thinking like a real-time programmer,
- giving you an idea of the kinds of problems to watch for. For a real
- education on concurrent programming, head to your library or book store
- and look for a book on operating systems.
-
-
-
- 3.1.1 PASSING PARAMETERS TO TASKS
-
-
- When you create a task, you can pass a parameter to it. For example, a
- BBS program needs to tell a task which node it is, so that the task
- knows which serial port to use for i/o. The parameter you pass is
- "untyped," meaning that it can be any type of variable. You must be
- familiar with how Turbo handles untyped variables.
-
- The sample program TEST1.PAS shows how to pass a word variable to a
- task. You can pass any kind of variable, including records, arrays,
- and even files.
-
- One thing to remember is that when you pass an untyped parameter to a
- task, you are actually passing the address of the parameter, not the
- parameter itself. Therefore, if you pass the task a paramater and then
- modify the parameter, the task may see the new value instead of the old
- value. It will all depend upon where task switches occur.
-
- As a general rule, parameters you pass to a task should be global
- variables or typed constants. Global variables and typed constants are
- both in the data segment. Local variables are declared on the current
- task's stack, and cannot be assured of existing for very long. If you
- a procedure passes one of its local variables to a task that it's
- creating, and then the procedure returns, that local variable is
- "thrown away" and its space can be reused by other procedures. That
- would cause the value of the parameter you passed to the task to change
- unpredictably.
-
-
- 3.1.2 SHARED DATA
-
-
- Problems can occur when two or more tasks are using the same global
- variables. If two or more tasks have access to the same variable, you
- need to consider carefully what will happen if two tasks access the
- variable concurrently. This pseudo-code example shows two tasks.
- task_a is computing the sum of an array of Reals. Task_b is clearing
- the values in the array.
-
-
- CONST
- data_size = 1000;
- VAR
- data: ARRAY [1..data_size] OF Real;
-
-
- PROCEDURE task_a;
- .
- .
- .
- sum := 0.0;
- FOR i := 1 TO data_size DO
- BEGIN
- sum := sum + data [i];
- switch_task;
- END;
- .
- .
- .
- END;
-
-
- PROCEDURE task_b;
- .
- .
- .
- FOR i := data_size DOWNTO 1 DO
- BEGIN
- data [i] := 0.0;
- switch_task;
- END;
- .
- .
- .
- END;
-
-
- Do you see what happens if task_a is computing the average at the same
- time task_b is clearing the array? The average will end up being
- incorrect, because the data being averaged is being changed while the
- average is being computed. Obviously, this example is contrived.
- Nobody in their right mind would call switch_task inside those loops.
- That causes many more context switches than are necessary.
-
- One way to avoid the problem in this particular example is not to call
- switch_task inside either of the loops. Then you could be sure that an
- average would not take place while you were clearing the array, and
- array clearing would not take place during an average.
-
- You cannot always avoid calling switch_task, however. Suppose that
- floating point addition on your computer was so slow that it took many
- seconds to compute the average. You may have other tasks that cannot
- afford to be denied CPU time for more than a fraction of a second.
- What do you do?
-
- The solution here is to create a flag that indicates when a task is
- using the data array. When one task is using the data array the flag
- will be set to True, indicating that no other task should access it.
-
-
- CONST
- flag: Boolean = False;
-
-
- PROCEDURE task_a;
- .
- .
- .
- WHILE flag DO
- switch_task;
- flag := True;
- sum := 0.0;
- FOR i := 1 TO data_size DO
- BEGIN
- sum := sum + data [i];
- switch_task;
- END;
- flag := False;
- .
- .
- .
- END;
-
- PROCEDURE task_b;
- .
- .
- .
- WHILE flag DO
- switch_task;
- flag := True;
- FOR i := data_size DOWNTO 1 DO
- BEGIN
- data [i] := 0.0;
- switch_task;
- END;
- flag := True;
- .
- .
- .
- END;
-
-
- Do you see what's going on here? Before task_a does an average, it
- checks the flag to see whether someone else is messing with the data
- array. If someone is, then it waits until the data structure is
- available, sets the flag to indicate that it now "owns" the data array,
- and proceeds to compute the average. When the average is finished,
- task_a resets the flag, to allow any other task which is waiting for
- the data array to have access. task_b is doing exactly the same thing.
-
- Now both tasks can go on calling switch_task even while messing with
- the data array, without concern that some other task will access the
- data array at the same time. This technique will work for any number
- of tasks.
-
-
-
- 3.1.3 WHEN TO SWITCH TASKS?
-
-
- Obviously, the examples in 3.1.2 switch tasks far too often. The
- program will spend more time bouncing from one task to another than it
- will doing anything useful! If your loop is too time consuming to
- leave out task switches, and switching tasks during every iteration of
- the loop is too often, try something like this:
-
-
- FOR i := 1 TO 10000 DO
- BEGIN
- IF i MOD 100 = 0 THEN
- switch_task;
- do_something_useful;
- END;
-
-
- This will switch tasks every hundreth iteration of the loop.
-
- If your program is going to do something that takes a while, like disk
- i/o, it should probably switch tasks before doing so to let the other
- tasks get some time before the long delay occurs. In fact, if you are
- doing several lengthy disk operations in a row, call switch_task before
- every one.
-
-
- Assign (inf, 'INPUT.DAT');
- switch_task;
- Reset (inf);
- Assign (outf, 'OUTPUT.DAT');
- switch_task;
- Rewrite (outf);
-
-
- Many programs have to wait for input at some point. Input loops are a
- perfect place to switch tasks. In fact, any time a task cannot proceed
- because its input is not ready, or for any other reason, it should
- switch tasks.
-
-
- WHILE NOT KeyPressed DO
- switch_task;
- ch := ReadKey;
-
-
- It is a matter of judgement where task switches should occur. It will
- depend upon the program and circumstances around each operation.
-
-
-
- 4.1 REVISION HISTORY
-
-
- Version 1.0, MTASK10.ARC.
-
- Original release by Wayne E. Conrad
-
- Version 1.1, MTASK11.ARC.
-
- Minor changes to documentation, including using spaces instead of
- tabs. ARC file now includes the original documentation in
- Multi-Edit format, as well as the printable file.
-
- Version 2.0, MTASK20.ZIP.
-
- When a task is terminated, Mtask tries to not assign that task's
- id to a new task for as long as possible. In order to accomplish
- this, task id's are now words (1..65535).
-
- The create_task procedure was often returning bogus error numbers
- when in fact no error had occured. Fixed.
-
- The get_task_info procedure is gone, in preparation for a change
- in the way task information is stored. If you need this
- procedure, you should be able to copy it out of version 1.1
- without any problems.
-