Multitasking

license

How tasks are dealt with sets the scene for the whole system. Tasks can be schedule by multiple interrupt sources. Each interrupt source is allocated a different priority. All interrupt sources have a higher priority than the clock interrupt. The clock interrupt is divided into 3 sub priorities. The lowest priority consumes all spare cpu time. Within a priority task sheduling is on a co-oporaive basis. That is the task gets the CPU until it releases it. Task switching within a level is done at FORTH word boundries making in necessary to save the FORTH enviroment only.

Switching between levels is done on a pre-emtive basis, the full machine state is saved with no reference to what is actually running.

See real_time_clock.html

 
	ram_variable %ticks
	ram_variable xclock+
	ram_variable xclock-
	ram_variable %today

	\ set to try when ip gets time from gateway
	\ the system looks after this
	ram_variable %ticks_set


	    
	ram_variable XAGAIN           ( Time to restart level 0)
  	 
xbase

The clock interrupt interrupts the system every 10d msec. The interrupt does not raise the task priority level unless the number of interrupts equals the value stored in xbase. For example if xbase is set to 10d the clock will raise the priority level every 100d msecs.

 
    dictionary_variable  xbase
	01 xbase dt!


	 
sleep wake

The following are constants stored in the tasks STATUS to control the state of the task.

All this stuff was rewritten so a task could destoy itself by writing kill to the status area. This allows a server task to create a task to service a open connection and forget about it. The child deals with itself. A much more elegent solution.

     
	0 
	DUP CONSTANT sleep           CELL+   \ ignore task
	DUP CONSTANT wake            CELL+   \ active task
	DUP CONSTANT test            CELL+   \ look at task activation time
	DROP
	 

The multitasker has several list. List zero has the highest priority, list on the next highest and so on. _xlevel points to the list currently running.

 
	ram_variable _xlevel 
	 

The priority to be executed when multitasker to be restated, this is looked at when ever the the processor state moves from supervisor to user.

 
	ram_variable _xlevel_next
	 
xnext (suspend a task)

We may not have protected memory, but boy we have fast task switches. This word saves the forth enviroment on the parameter stack, saves the current stack pointer into the user area and returns to the multitasker.

 
	CODE xnext ( --)
		LP S -) MOV  \ local pointer 
		OP S -) MOV  \ object pointer
		R S -) MOV    
		S (S) U) MOV
		_activation> user_base - U) A0 MOV
		#activation_task_link 0) A0 MOV  
		A0 ) JMP
	 
stop

Brings things to an end

 
	CODE stop
		00 # TRAP
	NEXT
	 
xpause

Exit to the multitasker, the task will be restarted when things are looked at again. If the task is on #clock_low this will be as soon as load permits. If the task is on a higher level this will be when the hardware restarts that level. #clock_medium and #task_level_clock_high are looked at every 100 msecs. This can be altered by storing a different value into xbase. The interrupt tasks are only looked at when a interrrupt occures and the interrupt raises the priority level.

 
	: status ( -- addr)
		@u activation_status
	;

	: xpause  ( --)
		wake status W!   
		xnext
	;
	 
xoff

Use if the task is to remain but not started again. If the task is to be restarted by another task use xsleep.

 
	: xoff ( --)    
		sleep status W!
		xnext
	;
	 
xsleep

We will not be actived unless someone else wakes us up. Used in the form.

: yyyy ..... xsleep start_something_that_can_wake_us xpause ... ;

We do it this ways so we won't miss an event. If we start the external activity before setting the STATUS area the external event could set it before we do and the event will be missed forever.

 
	: xsleep ( --)  
		sleep status W!
	;
	 
xwake

Use is pretty limited, used in type routines when tranferring rotating buffer characters to input buffer, without echo.

 
	: xwake ( --)
		wake status W!
	;
	 
xtest

Set the STATUS to test, used in the form:

: xxxx ..... xtest n xwait ...... ;

Or to be more specific.

: yyyy ..... xtest start_something_that_can_wake_us n xwait ... ;

See xtimeout? for futher clarification.

 
	: xtest  ( --)  
		test user_base activation_status W! 
	;
	 
xwait

Take the current time, add the requested time delay and use that as the reactivation time. Go to sleep.

 
	: xwait  ( n --)
		xclock+ @ + 
		_task_restart_time !
		xnext
	;
	 
xcycle

The time is added to the last _task_restart_time and used as the next _task_restart_time. If the number of executions is really important then use this. It's use is not recommented as you gain very little ( you still have to use the actual delay for time calculations) and the risks are great ( you can lose a years time and be locked out for a year).

 
	: xcycle ( n --)
		_task_restart_time @ +
		_task_restart_time !
		xnext
	;
	 
xtimeout?

When you do an xwait for an external event, the external event changes the STATUS to wake when the event times out. If the event didn't time out the staus area will still be set to _test. Obviously this word was to be used before the STATUS is altered by other multitasker words.

flag is true if timeout

 
	: xtimeout? ( --flag )
		status W@ test =
	;

	 

Facility variables

Being a multitasking system, several tasks may want to use the same resources at the same time. Whan an item is claimed it is linked into _facility_head so that the items can be realeased if the task aborts.A task claims a facility by having it's user_base address written to the facility variable.

All the facilites used by a task are link back to this head.

 
		uvariable	_facility_head
	 
grab

Try and claim the facility without returning to the multitasker, if you fail return to the multitasker and try again later.

 
	
	: grab ( addr --)
		BEGIN
			_lock_word
			DUP @ 
			0= IF		\ addr (--
			    status OVER !
				\ link into facility list so about can release
				_unlock_word
				_#facility_link + _facility_head link_double
				EXIT
			THEN
			_unlock_word
			DUP @ status = IF	\ addr (--
				DROP
				EXIT
			THEN
			xpause
		AGAIN
	;
	 
get

get is a little kinder to the system than grab, it returns to the multitasker and then tries to get the facility when next started.

 
	: get ( addr --)
		xpause 
		grab 
	;

	 
release

There is code that has assumed that release doesn't exit to the multitasker ( grab is used directly after). We assume tasks don't release if they don't claim.

 
	\ There is code that depends on this definition of release
	\ if you change bump the version number
	01 CONSTANT _#release_version
	: release ( addr --)
		DUP @ 0<> IF
			DUP _#facility_link + unlink_double
			zero SWAP !
		ELSE
			DROP
		THEN
	;
	 

Dual port must use activation status, further the claim is not linked in has dual port stuctures are locked in history.

   
	\ Non standard
	\
	\ There is code that has assumed that release doesn't 
	\ exit to the multitasker ( grab is used directly after)
	\ ##### get rid of when dual port stuff is sorted out
	: port_release ( addr --)
		DUP @ status = IF
			zero SWAP !
		ELSE
			DROP
		THEN
	;
	 
xgrab

Try and get the facility for a little while and if not successful give up. A flag is returned to indicate the outcome.

   
	CREATE $can't_claim ," Can't claim facility."

	\ n1 is the time to wait
	\ addr is the facility address
	\ $ is a error string
	: xgrab ( n1 addr --0|$)
		BEGIN
			OVER 0>
		WHILE
			_lock_word
			DUP @ 0= IF		\ count addr (--
			    status OVER !
				\ link into facility list so about can release
				_unlock_word
				_#facility_link + _facility_head link_double
				DROP
				FALSE
				EXIT
			THEN
			_unlock_word
			DUP @ status = IF	\ count addr (--
				2DROP
				FALSE
				EXIT
			THEN
			xtest 0A xwait
			SWAP 0A - SWAP
		REPEAT
		\ get to here and the game is lost
		2DROP
		$can't_claim
	;	

	 

Areas claimed in dual port must use ther activation status. Further they are not locked in as dual port structures are locked in history.

 
	\ n1 is the time to wait
	\ addr is the facility address
	: port_xgrab ( n1 addr --false|$)
		BEGIN
			OVER 0>
		WHILE
			_lock_word
			DUP @ 0= IF		\ count addr (--
			    status SWAP !
				_unlock_word
				DROP
				FALSE
				EXIT
			THEN
			_unlock_word
			DUP @ status = IF	\ count addr (--
				2DROP
				FALSE
				EXIT
			THEN
			xtest 0A xwait
			SWAP 0A - SWAP
		REPEAT
		\ get to here and the game is lost
		2DROP
		$can't_claim
	;