|
||
PRE-EMPTIVE MULTITASKING EXPLAINED
TIME-SLICINGPre-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 TASKThere 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
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 CHARACTERISTICSThe 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:
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 AREAThe 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 TABLEIn 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
VECTORED WORDS
OTHER TASK VARIABLES
PARAMETER STACKAn 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 STACKThe 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 & PADThe 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 TASKA 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 MULTITASKINGInterrupts 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 LAYOUTIf 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 PLUS ONE BACKGROUND TASKThis 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 TASKSThis 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 TASKSHere 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 VARIABLESBecause 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 MULTITASKINGFor 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:
|