Go to Triangle Digital Support Home Page TDS2020F TECHNICAL MANUAL
Multitasking
Pre-emptive multitasking explained
Live website search
Enter key words
 

PRE-EMPTIVE MULTITASKING EXPLAINED

Co-operative multitasking

Co-operative multitasking

Pre-emptive multitasking

Pre-emptive multitasking

TIME-SLICING

Pre-emptive Multitasking is based on the Co-operative scheme but you do not have to put in PAUSE to cause task switching. Provided it is not disabled with UNSLICE a task change is guaranteed to occur at least every 10ms (default.)

When a task switch occurs (pre-emptively, at PAUSE or its assembler equivalent) output compare register A of timer 3 is set 10ms ahead of the present time and the next task in the round-robin then takes over. If another co-operative switch is not encountered within 10ms there will be an output compare interrupt.

A machine code version of PAUSE is embedded in the interrupt routine and the present task will be saved and the next one in the loop restored. Because the interrupt can occur anywhere, this task switch is considerably more difficult than the co-operative one. More has to be saved but, on return, execution of the task will be faithfully restored at the point inside the interrupt where it was temporarily abandoned.

The time limit on execution of any task is governed by the number in variable %SLICE . This represents 814ns units and is set to 12288 (10ms) by default. It is a global variable and you can modify the value, even dynamically to prioritise tasks.

DEFINING A NEW TASK

There is only one task when you start development on the TDS2020F. However it represents a minimal circle of tasks because it points to itself. In the #ROBIN.TDS software this task is given the name OPERATOR . Add a new task like this:

 

$FDE8 CONSTANT FRED

38 52 80 FRED  BACKGROUND

Its name is FRED and it is inserted after the current task. If we subsequently define a third task:

 

$FEB4 CONSTANT HARRY
38 52 80 HARRY BACKGROUND

 

then HARRY will now come after the current task, and be followed by FRED . FRED still comes before the current task closing the circle of three tasks.

Execution of the name of a task returns the address of a task-record where all information on it is stored; typing HEX FRED return will give hex FDE8 and HARRY return results in FEB4.

The address of the task-record can also be obtained from the word STATUS , which gives the address of the task that executes the word. You can see the address of the initial task by typing STATUS U. return at the PC.

The BACKGROUND task definition should be in a word executed during power-up initialisation. It is convenient to define all tasks in a separate module and file #TASKS.TDS is provided as an example that can be edited to suit your needs. Here three other tasks are added to the principal OPERATOR task giving four in total. The limit is only RAM availability.

CONTROLLING TASK CHARACTERISTICS

The size and characteristics of each task are governed by four parameters. Three numbers and the task name are fed into BACKGROUND . The different areas of the task-record are as follows:

 

80 bytes user area

minimum is 80 (hex 50)

52 bytes parameter stack

minimum depends on application, probably not fewer than 36 but may need 52 or more

38 bytes TIB+return stack

minimum depends on application, probably not fewer than 16 but may need 38 or more. Use at least 132 if a terminal task because the area is then a Terminal Input Buffer followed by the return stack.

34 bytes dictionary & PAD

minimum 34 if PAD not needed or zero if there is no formatted number output or use of HERE

 

The total size of the example task-record is 204 bytes. If it starts at $FDE8 begin the next task-record at $FEB4. The size of the dictionary and PAD are not explicit in the definition of a task, the number 34 above is not a parameter to BACKGROUND . It is the amount of memory left over before the next task (or other memory allocation) begins.

The sections of the task-record are now considered in detail.

THE USER AREA

The first six bytes of the user area are concerned with the multitasking scheme, the rest are variables usually available only to the task in question.

Hex 50 (decimal 80) bytes are already defined in the user area so this is the minimum number needed for BACKGROUND when defining a task. In the OPERATOR task there are 98 bytes, giving space for nine 16-bit user variables.

Any memory requested above decimal 80 is available to the application program for use as variables. In the example in DEFINING A NEW TASK, page 187, decimal 80 bytes are requested, so no other user variables can be added. By making it bigger you can define them as follows:

 

$50 USER URSULA

$52 USER JANE

$54 USER FLORENCE . etc

 

Bytes 0 to $4F are already defined by the system so start with byte $50 (decimal 80). The only intrinsic limitation to the number of user-variables that can be created for an application program is the amount of memory. However generally use a normal variable whenever only one task will access it, or where a mailbox is needed for passing a message from one task to another.

User-variables are distinct from ordinary ones. This is because each task has its own copy of the variable and they can be different values without causing any problem. For example the task FRED can be in base decimal and task HARRY in hex. The user-variable BASE will be decimal 10 in the first task and 16 in the second. Task FRED might be processing decimal numbers input by an operator at a terminal while HARRY is sending a hex listing to a printer.

If BASE is accessed from different tasks you get the user-variable BASE of each task. The memory locations are not the same and identical words are reaching different variables. To access a user-variable in a different task use the word HIS . This takes the name of the other task and the user-variable and returns the memory address needed. For example:

 

FRED BASE HIS @ .

 

will print the current base in which task FRED is operating, irrespective of the task which executes this code.

When a task is set up by BACKGROUND the set of user-variables is copied over to the user area of the new task. That task is then free to change them if it wants. Usually the OPERATOR task sets up other tasks, but any task can create another. The user-variables of the instructing task are the ones passed over to the new one.

USER AREA TABLE

In the following table showing the make-up of the user area the byte number given is the offset from the start of the task-record. See the WORD LIST, page  356, for a full description of the use of each system user-variable.

TASK SUPPORT

Byte no (hex)

Function

 

00

01

Contains $00 (no operation) and $10 (jump) when the task is asleep and $081F (software interrupt decimal 15) when the task is awake. The word STOP puts it to sleep and ACTIVATE awakens it.

02

03

Contains the record address of the next task in the circle. Together with a previous $10 it forms a complete jump instruction used when the task is asleep. The jump closes the loop without the task having any effect.

04

05

These keep the stack pointer for the task when it is not running. While a different task is in charge of the microprocessor all details of the task are stored on the parameter stack and then the stack pointer is placed in this location so that the task can be restored when its turn comes around again.

VECTORED WORDS

Byte no (hex)

Function

 

 

06

07

not named

not used

08

09

'SOURCE

xt of the word SOURCE

0A

0B

'EMIT

xt of the word EMIT

0C

0D

'KEY

xt of the word KEY

0E

0F

'KEY?

xt of the word KEY?

10

11

'?NUMBER

xt of the word ?NUMBER

12

13

'ACCEPT

xt of the word ACCEPT

14

15

not named

not used

16

17

'PUT

xt of the word PUT

18

19

'ERROR

xt of the word ERROR

OTHER TASK VARIABLES

Byte no (hex)

Function

 

 

1A

1B

SP0

Base of stack of this task

1C

1D

RP0

Base of return stack of this task

1E

1F

not named

Start of terminal input buffer source

20

21

WIDTH

Maximum characters in name field

22

23

not named

Return stack pointer for CATCH & THROW

24

25

DP

Dictionary pointer

26

27

not named

Vocabulary linking

28

29

VDP

Address of next variable

2A

2B

BLK

Block being accessed

2C

2D

not named

Unused or BLK extension

2E

2F

>IN

Pointer into block

30

31

OUT

Position of output device column

32

33

not named

Screen accessed by editor, 0 = terminal input

34

35

CONTEXT

Points to nfa of last word in search vocabulary

36

37

CURRENT

Points to nfa of last word in extensible vocabulary

38

39

STATE

0 = interpret, non-zero = compile

3A

3B

BASE

Number base for input/output

3C

3D

DPL

Decimal point place in number conversions

3E

3F

CSP

Current stack position for compile checking

40

41

not named

Editing cursor position

42

43

HLD

Addr of latest character of text during number formatting

44

45

#LCD

Number of the LCD in use by this task

46

47

AT

Cursor position on this task's LCD

48

49

#TIB

Number of characters in terminal input buffer

4A

4B

SPAN

Count from EXPECT

4C

4D

not named

System use by LEAVE

4E

4F

not named

Current source I.D.  0 = normal  -1 = inside EVALUATE

PARAMETER STACK

An exact recommendation for the size of the stack cannot be made but experience shows that a real application with complex interrupts can use 52 or more bytes. Allow 36 or more initially in the absence of any other guide. This will allow up to 18 16-bit items on the Forth stack in worst case (of which 11 are needed for system use). A task that will not be interrupted can probably be given a stack 22 bytes. The main OPERATOR task has 218 bytes available.

TERMINAL INPUT BUFFER & RETURN STACK

The next part of the task-record has two functions. First comes the Terminal Input Buffer, growing toward high memory, and then the Return Stack proceeding to low memory. Most background tasks do not need a Terminal Input Buffer because if one is needed at all in the system it is usual to use that in the principal, or OPERATOR task. It will normally only be required if a background task has to input and execute Forth commands coming from outside. It is unlikely, but suppose an application needed two or more programmers debugging and compiling Forth on a single TDS2020F, then one would use the OPERATOR task and others would each need a background task with its own TIB.

A possible real scenario might be a series of TDS2020F computers generating Forth source code in response to inputs. If these text streams were fed back to a single TDS2020F, different background tasks-each with its Terminal Input Buffer-could be used to receive and then process the code.

A Terminal Input Buffer should be at least 3 bytes more than the longest text string expected, say 84 bytes in total-the OPERATOR task's is this length. However in most cases no memory at all need be allocated to a TIB.

The Return Stack is a different matter, it is essential and two bytes are needed for every Forth nesting level. Again no firm guidance can be given but experience shows 16 bytes may be sufficient. However 38 bytes or more may be needed for a complicated program. The OPERATOR task has 48 bytes but at run-time the 84 bytes Terminal Input Buffer is not normally needed giving a total of 132 bytes.

DICTIONARY & PAD

The size of the final part of the task-record is not a parameter fed into BACKGROUND like the other areas. It is the number of bytes left over before memory is reached which is used for something else-say the next task, end of RAM, or regular variables or arrays. For example if these two tasks are defined:

 

$FDE8 CONSTANT FRED    38 52 80 FRED  BACKGROUND

$FEB4 CONSTANT HARRY   38 52 80 HARRY BACKGROUND

 

then the hex CC (decimal 204) bytes from the start of one task to the start of the next are made up from 80 bytes user area, 52 bytes parameter stack and 38 bytes TIB & Return Stack leaving 34 bytes for dictionary and PAD. This is the minimum amount if the task is to perform output number formatting with words like U. .

STARTING AND STOPPING A TASK

A task is given a job to do by the word ACTIVATE . It is only used in a colon definition, which takes one of two forms:

 

: MANY  FRED ACTIVATE  BEGIN 7 EMIT 500 MS AGAIN ;

: ONCE  FRED ACTIVATE  7 EMIT STOP ;

 

MANY starts a task FRED which rings the bell on the terminal every 500 milliseconds. ONCE sounds it only one time and then task FRED is deactivated.

Having started a job FRED can be stopped by

 

q       execution of STOP as above in its own task

q       execution of FRED HALT by another task

 

Note that any task can start another one, and can also suspend any other.

The memory allocation for tasks is best done inside file #TASKS.TDS, which can be edited to suit your needs as described above in the section DEFINING A NEW TASK, page 187. This includes a word TASKS , which is then used in the power-up word of your application. It constructs the task-records in memory and can be followed by the words that activate the different tasks. For example try this complete program that can be run from a Flash-EEPROM:

 

INCLUDE #ROBIN \ Compile multitasking software

INCLUDE #TASKS \ Define multiple tasks and add

               \  pre-emptive capability

: MANY  FRED ACTIVATE  BEGIN 7 EMIT 500 MS AGAIN ;

: WORK  TASKS MANY START ;

 

In this example a background task FRED executes the loop in MANY and the OPERATOR task runs Forth (since it goes on to START ). The other two tasks defined inside the file #TASKS.TDS remain dormant. They are in the round robin but each consists only of a jump instruction in the task-record as described in USER AREA, page 189. After compiling the above into Flash-EEPROM, type SET WORK return to make a stand-alone system.

Even if a task is operational it can be presented with a new piece of work by another word which includes ACTIVATE . If the following definition were added to the above program then typing ALIVE would stop the bell ringing and substitute a repeating message on the terminal:

 

: ALIVE

   FRED ACTIVATE

   BEGIN  CR ." I'm alive!  " 500 MS  AGAIN ;

 

Some applications are best constructed with a pool of available tasks where each is given a job to do until finished. It can then be reallocated to another requirement.

INTERRUPTS AND PRE-EMPTIVE MULTITASKING

Interrupts are compatible with Co-operative (and Pre-emptive) multitasking but you need to consider what priority level of interrupts will be accepted by each task. By default the first task ( OPERATOR ) will accept only NMI or priority 7 interrupts since the Forth ROM at power-up sets the condition code register to an interrupt mask of 6. The same is true for a new task started with ACTIVATE . However if you changed the mask level in your own program at power-up it will not apply to tasks started with ACTIVATE , they will still only accept NMI and priority 7 interrupts.

For example in the library routine #SHELTER.TDS (which provides interrupt-driven communications) the power-up word SHELTER contains EIS . This sets the condition code register to an interrupt mask of 0 so that any interrupt of priority 1 to 8 will be accepted. You will need EIS immediately after each ACTIVATE in your program so that other tasks will do the same whenever interrupts with priority level 6 or below are present.

When using interrupt-driven software like #SHELTER.TDS or #SERIAL.TDS, the initialisation (such as SHELTER or 2BAUD ) should be done inside the appropriate task (i.e. after ACTIVATE ) rather than at power-up before the tasks are launched.

TASK RECORD LAYOUT

If you are not using the TDS2020DV development board, the only free RAM for task creation is inside the H8/532 microprocessor. This section considers how to allocate that memory.

Even when the application is finalised in Flash-EEPROM there are (decimal) 512 free bytes starting at hex FD80. This is enough to have up to four round robin tasks, the OPERATOR task and three others.

Some suggested memory allocations follow. You can include this code in your applications program. Note that stacks can be traded for variables or PAD , so alter these to suit the particular case if necessary. The memory allocation does not have to be the same in each task. The third example is taken from file #TASKS.TDS, you can substitute the first or second example in that file if fewer tasks are needed, or alter the numbers to meet different requirements.

Operator + 1 task Operator + 2 tasks Operator + 3 tasks

Operator + 1 task Operator + 2 tasks Operator + 3 tasks


 

OPERATOR PLUS ONE BACKGROUND TASK

This gives full output number-formatting to both tasks but there is no PAD . For almost all applications this will cause no problem, see RAM ALLOCATION IN A STAND ALONE SYSTEM, page 240. To add a PAD to the operator task comment out the line $FDA2 VDP ! . To add a PAD to task FRED , change its definition to read $FE38 CONSTANT FRED . The space for variables will be correspondingly reduced in both cases.

 

\ $FD80 \ Dictionary address of OPERATOR in

        \ stand-alone system. Also number

        \ formatting area for task OPERATOR,

        \ but no PAD

$FDA2 VDP ! \ Start of address of up to

            \ 117 16-bit variables

$FE8C CONSTANT FRED  \ Start of task FRED

: TASKS ( - ) \ Create 1 other task

   FRED 2@ [ STATUS $10 ] 2LITERAL

                   \ will have this number if set up

   D= NOT          \ true if tasks not set up yet

   IF              \ create set of deactivated tasks

      FRED         \ start of tasks

      $FF80 OVER - \ size of task records

      ERASE        \ set all task record bytes to 0

      50 80 80 FRED  BACKGROUND

                   \ with number formatting, no PAD,

                   \ return stack 25, stack 40 items

   THEN SLICE ;   \ add pre-emptive task-switch

OPERATOR PLUS TWO BACKGROUND TASKS

This gives full output number-formatting to all three tasks including OPERATOR but there is no PAD . For almost all applications this will cause no problem.

 

\ $FD80 \ Dictionary address of OPERATOR in

        \ stand-alone system. Also number

        \ formatting area for task OPERATOR,

        \ but no PAD

$FDA2 VDP ! \ Start of address of up to

            \ 35 16-bit variables

$FDE8 CONSTANT FRED  \ Start of task FRED

$FEB4 CONSTANT HARRY \ Start of task HARRY

: TASKS ( - ) \ Create 2 other tasks

   FRED 2@ [ STATUS $10 ] 2LITERAL

                   \ will have this number if set up

   D= NOT          \ true if tasks not set up yet

   IF              \ create set of deactivated tasks

      FRED         \ start of tasks

      $FF80 OVER - \ size of task records

      ERASE        \ set all task record bytes to 0

      38 52 80 FRED  BACKGROUND

                   \ with number formatting, no PAD,

                   \ return stack 19, stack 26 items

      38 52 80 HARRY BACKGROUND

                   \ with number formatting, no PAD,

                   \ return stack 19, stack 26 items

   THEN SLICE ;   \ add pre-emptive task-switch

OPERATOR PLUS THREE BACKGROUND TASKS

Here things start to get a bit tight in a system not developed with a piggyback TDS2020DV board, leaving hex 8800 to FB7F as RAM even in the stand-alone system. However, the example file #TASKTRY.TDS shows that four tasks are readily feasible using normal Flash-EEPROM. In the layout below, only the OPERATOR and one other task are given output number formatting capability. In any case it is a good idea to have only one task doing the output to serial port or LCD to avoid resource-sharing problems.

The three extra tasks still have a full set of USER variables, but stack sizes have been reduced. Some adjustment might be needed once actual usage is known.

 

\ $FD80 \ Dictionary address of OPERATOR in

        \ stand-alone system. Also number

        \ formatting area for task OPERATOR,

        \ but no PAD

$FDA2 VDP ! Start of address of up to

          \ nine 16-bit variables

$FDB4 CONSTANT FRED  \ Start of task FRED

$FE78 CONSTANT HARRY \ Start of task HARRY

$FEFC CONSTANT JOE   \ Start of task JOE

: TASKS ( - ) \ Create 3 other tasks

   FRED 2@ [ STATUS $10 ] 2LITERAL

                   \ will have this number if set up

   D= NOT          \ true if tasks not set up yet

   IF              \ create set of deactivated tasks

      FRED         \ start of tasks

      $FF80 OVER - \ size of task records

      ERASE        \ set all task record bytes to 0

      32 50 80 FRED  BACKGROUND

                   \ with number formatting, no PAD,

                   \ return stack 16, stack 25 items

      16 36 80 HARRY BACKGROUND

                   \ no   number formatting, no PAD,

                   \ return stack  8, stack 18 items

      16 36 80 JOE   BACKGROUND

                   \ no   number formatting, no PAD,

                   \ return stack  8, stack 18 items

 THEN  SLICE ;   \ add pre-emptive task-switch

EXTRA VARIABLES

Because the microprocessor RAM is useful for task records, you may need extra space for variables. See also MORE VARIABLES, page 240.

Use library file #EXTVAR.TDS or #EXTVALU.TDS to push some variables over into the TDS2020F's 32-pin socket. Likewise #EXTSHEL.TDS and #EXTSER2.TDS for serial communications put their circular buffer in the 32-pin socket.

In the Flash Forth ROM a very large stack is allocated, mainly to help beginners. You can steal some of it. A minimum of 80 bytes is usually more than adequate for any multitasking application, but 218 bytes are available. The stack base is at hex FC7E and the limit is FBA4. You can use addresses FBA4 to FC2D inclusive for extra variables or serial port buffers, a total of 69 16-bit variables or 138 bytes.

The clock chip has some free RAM that can be used for variables-see USING CLOCK CHIP RAM, page 120.

Chips like Catalyst's 32k-byte CAT24WC256 can be added externally on the serial I2C bus to give you variables that are intrinsically non-volatile, although with a slower access time. The PCMCIA adapters and CAN bus adapters have suitable empty sockets.

ERRORS WHEN MULTITASKING

For an introduction see ERROR HANDLING, page 144. The following additional principles cover a multitasking system.

 

q       Each task is independent-one can crash and recover without affecting the others.

q       To recover from an error in a task, that task should have its own CATCH , including task OPERATOR .

q       If a task does not have a CATCH , an error it causes will cause the machine to crash.

q       An error in OPERATOR restarts OPERATOR but not the other tasks.

q       To reboot the whole system from an error in a task include RESTART after the task's CATCH and put BLUECOLD before the word TASKS (see comments in the code below).

q       After an error the machine is running wild. RAM locations might be corrupted and there can be no guarantee that the machine can recover. The real solution is to avoid error conditions.

 

The following code and run-time capture illustrates the point. The error code decimal -23 is correct for Address Error.

Note that the interactively typed 3 @ in the operator task does not restart the other task. The OPERATOR task recovers but the other task carries on quite correctly causing an error every 5 loops. It has no knowledge of the error that occurred in OPERATOR . The OPERATOR message CATCH MAIN does not appear because in this example START (interactive Forth) has its own CATCH that throws up the message ADDR ERROR.

If you put FRED ACTIVATE XX instead of FRED ACTIVATE XXTASK the machine crashes on error because then the second task has no CATCH .

 

Example code:

INCLUDE #ROBIN.TDS \ multitasker code

INCLUDE #TASKS.TDS \ create the tasks themselves

 

: XX (  -  ) \ The job for a second task

 EIS                          \ enable all interrupts

   0                            \ loop counter

   BEGIN                        \ start task loop

      CR ." IN XX " 1000 MS     \ show task running

      1+ DUP 4 MOD              \ true every 4 loops

      0= IF CR ." ABOUT TO GIVE ERROR " 1000 MS

 1 @ DROP          \ deliberate error

         THEN

 PAUSE                     \ allow task switch

   AGAIN ;                      \ carry on looping

 

: XXTASK ( --  ) \ CATCH loop for the second task

    BEGIN ['] XX CATCH          \ execute XX

            \ put RESTART here to reboot whole system

       CR ." CATCH XXTASK=" . 1000 MS \ error message

    AGAIN ;

 

: ~XXTASK (  --  ) \ Activate second task FRED

    FRED ACTIVATE XXTASK ;

 

: WORK ( -- ) \ Trial main word

 ~XXTASK                     \ start second task

    START ;                     \ Forth as OPERATOR

 

: MAIN ( --  ) \ Word entered at power-up

        \ Put BLUECOLD here if RESTART has been used

 TASKS                       \ create other tasks

    BEGIN ['] WORK CATCH        \ execute WORK

       CR ." CATCH MAIN=" . 1000 MS \ error message

    AGAIN ;

 

Runtime capture:

 

IN XX

from second task FRED

TDS2020 ANS Forth    Triangle Digital Support Ltd  Ver4.02

 

 

from first task OPERATOR

IN XX

 

IN XX

 

IN XX

 

ABOUT TO GIVE ERROR  CATCH XXTASK=-23

error from task FRED

IN XX 3

 

IN XX @ .  @ ?  - ADDR ERROR

error from task OPERATOR

 

 

IN XX

 

IN XX

 

ABOUT TO GIVE ERROR

 

CATCH XXTASK=-23

 

IN XX

 

Go to Triangle Digital Support Home Page Go to top   Next page