packages feed

language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/general/taskcont.mod

IMPLEMENTATION MODULE TaskControl;

	(****************************************************************)
	(*								*)
	(*	    The dispatcher of the operating system, and		*)
	(*	     related procedures and data structures.		*)
	(*								*)
	(*	    This version supports priority inheritance.		*)
	(*								*)
	(*	This is the portable part of the PMOS task control	*)
	(*	software.  The non-portable part is in module		*)
	(*	InnerKernel.						*)
	(*								*)
	(*	Programmer:	P. Moylan				*)
	(*	Last edited:	16 March 1995				*)
	(*	Status:		Working					*)
	(*								*)
	(*  Description:						*)
	(*	See individual procedures.  This is the module which	*)
	(*	does all task switching, also task creation.		*)
	(*	The kernel of the operating system consists of this	*)
	(*	module, module InnerKernel, and module Semaphores.  The	*)
	(*	module called Timer is also intimately linked to the	*)
	(*	kernel; it is technically not part of the kernel, but	*)
	(*	does most of its work by calls to kernel procedures.	*)
	(*								*)
	(*	The present kernel implementation allows a task to	*)
	(*	be in one of the following (mutually exclusive) states:	*)
	(*	    1. Active						*)
	(*		- running					*)
	(*		- ready						*)
	(*		- blocked waiting for a Lock			*)
	(*	    2. Inactive						*)
	(*		- sleeping					*)
	(*		- waiting for a semaphore Signal		*)
	(*		- waiting for a Signal with a timeout		*)
	(*		- dead						*)
	(*	In most of these cases, the state is defined by the	*)
	(*	kernel list on which the task is placed.  (All active	*)
	(*	tasks are on the same "Ready" structure, but we have	*)
	(*	enough other information to distinguish among the three	*)
	(*	active states.)  The exception is where a task is	*)
	(*	performing a timed semaphore wait, since there the	*)
	(*	task is simultaneously on two lists.  When such a task	*)
	(*	is woken up, it is run immediately in order to make	*)
	(*	indivisible the operation of removing the task from	*)
	(*	the two lists.  If this causes a higher-priority task	*)
	(*	to be preempted, a second task switch reinstates this	*)
	(*	higher-priority task before we leave the kernel.	*)
	(*	(Procedure RecheckPriorities looks after this detail.)	*)
	(*								*)
	(*	The priority of a task is normally a constant; but the	*)
	(*	priority can be increased in the case of an active	*)
	(*	task holding a Lock, where a higher-priority task is	*)
	(*	waiting to obtain that Lock.  In such cases the task's	*)
	(*	dynamic priority is equal to the priority of the	*)
	(*	highest-priority task which it is blocking.  As it	*)
	(*	turns out, there is no need to record the dynamic	*)
	(*	priorities for each active task; it is sufficient to	*)
	(*	maintain a global variable CurrentPriority, which holds	*)
	(*	the dynamic priority of the currently running task.	*)
	(*								*)
	(*	Interrupt tasks follow their own set of rules and do	*)
	(*	not fit into the above classification.  An interrupt	*)
	(*	task should never obtain a Lock,  perform a semaphore	*)
	(*	Wait (although a Signal is permitted), or Sleep.  The	*)
	(*	only possible states for an interrupt task are running	*)
	(*	(responding to an interrupt) or idle (waiting for an	*)
	(*	interrupt).  When an interrupt task is running, the	*)
	(*	variables CurrentTask and CurrentPriority continue to	*)
	(*	refer to the interrupted task.				*)
	(*								*)
	(****************************************************************)

FROM SYSTEM IMPORT
    (* proc *)	ADR;

FROM ConfigurationOptions IMPORT
    (* const*)	TimeSlicingEnabled, MaxTaskNumber, StackSize;

FROM InnerKernel IMPORT
    (* type *)	TaskSelector, FloatSaveSelector,
    (* proc *)	EnterKernel, LeaveKernel, NPXsave, NPXrestore,
		InitMainTask, MakeFloatSaveSelector,
		TaskInit, Transfer, IOTransfer, StartInterruptTask,
		DisconnectFromInterrupt;

	(* Procedure Transfer is the low-level primitive for the	*)
	(* stack switch operation, which is the most crucial part of	*)
	(* a task switch.  Procedures NPXsave and NPXrestore look after	*)
	(* saving and restoring the state of the NPX (Numeric Processor	*)
	(* Extension, i.e. the floating point hardware) coprocessor.	*)
	(* TaskInit introduces a new task into the system.		*)

FROM Storage IMPORT
    (* proc *)	ALLOCATE, DEALLOCATE;

FROM TerminationControl IMPORT
    (* proc *)	SetTerminationProcedure, Crash;

(************************************************************************)

TYPE
    TaskNumber = [0..MaxTaskNumber];
    InterruptType = CARDINAL;
    Lock = POINTER TO LockRecord;
    TaskPointer = POINTER TO TaskDescriptor;
    Task = TaskPointer;

    (* The kernel maintains a number of queues, which for the most part	*)
    (* are FIFO queues.  (Very occasionally, we need to remove a task	*)
    (* from the middle of a queue.)  Mainly because of the limitations	*)
    (* of the Modula-2 opaque export, we choose to represent a queue by	*)
    (* a single "head" pointer, and to implement a doubly linked list	*)
    (* structure with the aid of "previous" and "next" pointers in the	*)
    (* task descriptors.						*)

    TaskQueue = TaskPointer;

    (* Each task in the system has a task descriptor.			*)
    (* The fields in a task descriptor are:				*)
    (*	previous, next:	pointers to preceding and following task	*)
    (*		descriptors on the queue (i.e. whatever system queue	*)
    (*		this task descriptor happens to be on).			*)
    (*	name:	a character string describing the task.  Used only for	*)
    (*		diagnostic purposes.					*)
    (*	priority: task priority.  This is the task's base priority.	*)
    (*		Because of priority inheritance, the task may at times	*)
    (*		be running at a higher priority, but we don't need to	*)
    (*		record this in the task descriptor; it turns out to be	*)
    (*		sufficient to keep a single global variable (see	*)
    (*		CurrentPriority below) to hold the dynamic priority of	*)
    (*		the currently running task.				*)
    (*	WaitingFor: the Lock, if any, on which this task is blocked.	*)
    (*  FirstLock: the first in the list of Locks held by this task.	*)
    (*	selector: a selector for the task state of this task.  In the	*)
    (*		current implementation, most of the state information	*)
    (*		(register contents, etc.) is saved on the task's stack	*)
    (*		when a task switch occurs, and the selector field is	*)
    (*		used to save the stack pointer.				*)
    (*  FPselector: used for floating point save/restore.		*)
    (*	StackBase: the low address of the memory segment set aside for	*)
    (*		the stack. This is needed only when we kill a task, and	*)
    (*		wish to reclaim the memory used by its stack.		*)
    (*	TimeLeft: for a running task, this records how much of its	*)
    (*		current time slice the task still has left.  For a	*)
    (*		sleeping task, it shows how long to keep the task	*)
    (*		asleep (but not in terms of absolute time - see		*)
    (*		procedure Delay).					*)
    (*	InterruptHandler: true iff this task is an interrupt task.	*)
    (*	FloatingPointUser:  true iff this task is a user (or potential	*)
    (*		user) of the numeric coprocessor.			*)
    (*	dead:	true iff this is an unused descriptor or a descriptor	*)
    (*		for a task which has terminated.			*)
    (*	sleeping: true iff this is a task which is on the sleep queue.	*)
    (*	InterruptNo: interrupt number, used only for interrupt tasks.	*)
    (*	nextsleeper:	pointer to next task descriptor on the sleep	*)
    (*		queue.  Unused except when this task is a sleeping task	*)
    (*		or one which is timing out.				*)

    TaskDescriptor = RECORD
			previous, next	: Task;
			name		: NameString;
			priority	: PriorityLevel;
			WaitingFor,
			FirstLock	: Lock;
			selector	: TaskSelector;
			FPselector	: FloatSaveSelector;
			StackBase	: ADDRESS;
			TimeLeft	: INTEGER;
			InterruptHandler,
			FloatingPointUser,
			dead, sleeping	: BOOLEAN;
			InterruptNo	: InterruptType;
			nextsleeper	: Task;
		     END;

    (* A LockRecord holds all the data for a lock.  Note that we do	*)
    (* not maintain the traditional blocked list; in the more standard	*)
    (* implementations of a binary semaphore this would be needed to	*)
    (* know which task to unblock when the lock is released, but the	*)
    (* present algorithm leaves everything unblocked, relying on a	*)
    (* re-check at the time of a task switch.				*)
    (* The "next" and "previous" fields link together all locks held	*)
    (* by a single task.  This list would not be needed if we assumed	*)
    (* perfectly disciplined users of the locks; but we need to be	*)
    (* able to deal with things like tasks which exit while still	*)
    (* holding locks.							*)

    LockRecord = RECORD
		     Locked: BOOLEAN;
		     Holder: Task;
		     next, previous: Lock;
		 END (*RECORD*);

(************************************************************************)

VAR
    (* TaskTable is an array of task descriptors.  After procedure	*)
    (* InitialiseTaskControl returns, there will be two tasks in the	*)
    (* system: the initialisation task, and the null task.  The		*)
    (* remaining task descriptors will be on UnusedDescriptors,		*)
    (* available for the creation of new tasks.  A task descriptor is	*)
    (* allocated by procedure CreateTask, and is returned to		*)
    (* UnusedDescriptors by procedure TaskExit.				*)

    TaskTable: ARRAY TaskNumber OF TaskDescriptor;

    (* CurrentTask is the current non-interrupt task.  (Note: when an	*)
    (* interrupt task is running, CurrentTask continues to refer to the	*)
    (* interrupted task.)  LastTask is the task which was running just	*)
    (* before CurrentTask got control.  Most of the time LastTask is	*)
    (* irrelevant to us; but it is needed when dealing with some	*)
    (* complications which arise when waking a sleeping task.		*)

    LastTask, CurrentTask: Task;

    (* CurrentPriority is the _dynamic_ priority of the currently	*)
    (* running task.  If no other task is blocked on a Lock held by the	*)
    (* current task, then CurrentPriority is the current task's base	*)
    (* priority.  Otherwise, CurrentPriority is equal to the maximum	*)
    (* base priority of all tasks which are blocked (directly or	*)
    (* indirectly) by the current task as a result of their waiting to	*)
    (* obtain a Lock.							*)

    CurrentPriority: PriorityLevel;

    (* Because of the way we do priority inheritance, our treatment	*)
    (* of the "ready list" is somewhat unconventional.  This list holds	*)
    (* not only tasks which are ready, but also				*)
    (*		- the currently running task, and			*)
    (*		- all tasks waiting to obtain a Lock.			*)
    (* (Do not confuse obtaining a Lock with waiting on a general	*)
    (* semaphore.  Tasks doing a semaphore Wait are not on the ready	*)
    (* list.)  When we choose a task on the ready list and discover	*)
    (* that it is blocked, we switch to its blocker instead, with the	*)
    (* blocker inheriting the priority of the blocked task.		*)
		
    (* Note: priority level 0 is special - there is normally only one	*)
    (* task, namely the null task, at priority 0.			*)

    ReadyList: ARRAY PriorityLevel OF TaskQueue;

    (* InterruptTaskActive = TRUE iff an interrupt task is running.	*)
    	
    InterruptTaskActive: BOOLEAN;

    (* UnusedDescriptors is a queue of all task descriptors which are	*)
    (* unused, i.e. which are available for use as descriptors of newly	*)
    (* created tasks.							*)

    UnusedDescriptors: TaskQueue;

    (* The timequota array holds the time-slice quota, in units of	*)
    (* clock ticks, for each priority level.  Quotas are based on a	*)
    (* task's base priority, not on its inherited priority.		*)

    timequota: ARRAY PriorityLevel OF CARDINAL;

(************************************************************************)
(*			  KERNEL QUEUE OPERATIONS			*)
(************************************************************************)

PROCEDURE CreateQueue (VAR (*OUT*) KQ: TaskQueue);

    (* Creates an initially empty kernel queue.	*)

    BEGIN
	KQ := NIL;
    END CreateQueue;

(*************************************************************************)

PROCEDURE AddToQueue (VAR (*INOUT*) Q: TaskQueue;  T: Task);

    (* Adds task T to the tail of queue Q.	*)

    VAR tail: TaskPointer;

    BEGIN
	IF Q = NIL THEN
	    Q := T;
	    WITH T^ DO
		previous := T;  next := T;
	    END (*WITH*);
	ELSE
	    WITH Q^ DO
		tail := previous;  previous := T;
	    END (*WITH*);
	    tail^.next := T;
	    WITH T^ DO
		previous := tail;  next := Q;
	    END (*WITH*);
	END (*IF*);
    END AddToQueue;

(************************************************************************)

PROCEDURE TakeFromQueue (VAR (*INOUT*) Q: TaskQueue): Task;

    (* Removes the first entry from queue Q and returns it.		*)
    (* Assumption: the caller has already verified that the queue is	*)
    (* nonempty.							*)

    VAR result: Task;  tail: TaskPointer;

    BEGIN
	result := Q;  tail := Q^.previous;
	IF tail = Q THEN
	    Q := NIL;
	ELSE
	    Q := Q^.next;  Q^.previous := tail;  tail^.next := Q;
	END (*IF*);
	WITH result^ DO
	    previous := NIL;  next := NIL;
	END (*WITH*);
	RETURN result;
    END TakeFromQueue;

(************************************************************************)

PROCEDURE LeaveQueue (VAR (*INOUT*) KQ: TaskQueue);

    (* Removes the current task from queue KQ.  The caller is		*)
    (* responsible for knowing that the current task really is on KQ.	*)

    BEGIN
	WITH CurrentTask^ DO
	    IF next = CurrentTask THEN
		KQ := NIL;
	    ELSE
		previous^.next := next;  next^.previous := previous;
		IF KQ = CurrentTask THEN
		    KQ := next;
		END (*IF*);
	    END (*IF*);
	    previous := NIL;  next := NIL;
	END (*WITH*);
    END LeaveQueue;

(************************************************************************)
(*			      THE NULL TASK				*)
(************************************************************************)

PROCEDURE NullTask;

    (* The only function of the null task is to soak up processor time	*)
    (* when no other task is able to run.				*)

    BEGIN
	NotUsingFloatingPoint;
	LOOP (* Do nothing *) END (*LOOP*);
    END NullTask;

(************************************************************************)
(*			    	DISPATCHER				*)
(************************************************************************)

PROCEDURE TaskSwitch (T: Task);

    (* Performs a task switch from the current task to task T.  It is	*)
    (* assumed that the current task has already been moved to the	*)
    (* appropriate kernel queue.					*)
    (* This is, in principle, a system-dependent procedure, whose	*)
    (* detailed implementation depends on the processor architecture	*)
    (* and on compiler conventions.  To keep this module portable, a	*)
    (* separate procedure InnerKernel.Transfer is called to handle the	*)
    (* system-dependent part.						*)

    (* Special case: if an interrupt task is running, the actual task	*)
    (* switch is deferred until the interrupt task has finished		*)
    (* processing the current interrupt and has reached its next call	*)
    (* to WaitForInterrupt.  Procedure Transfer also looks after this	*)
    (* detail.								*)

    BEGIN
	IF T^.dead THEN
	    Crash ("Task switch to nonexistent task");
	END (*IF*);
	LastTask := CurrentTask;

	IF T <> LastTask THEN

	    CurrentTask := T;

	    (* Save and restore the floating point state if necessary.	*)
	    (* Notice that if an interrupt task is running (i.e. if	*)
	    (* InterruptTaskActive = TRUE), the saved state is that of	*)
	    (* the interrupted task, not the interrupt task.  This is	*)
	    (* the appropriate action since there is a system-wide	*)
	    (* assumption that interrupt handlers may not perform	*)
	    (* floating point operations.				*)

	    IF LastTask^.FloatingPointUser THEN
		NPXsave (LastTask^.FPselector);
	    END (*IF*);
	    IF T^.FloatingPointUser THEN
		NPXrestore (T^.FPselector);
	    END (*IF*);

	    Transfer (LastTask^.selector, T^.selector);

	    (* By the time we get back to here, one or more other	*)
	    (* tasks have been running; and we resume at this point	*)
	    (* by virtue of another task having executed Transfer.	*)

	END (*IF*);
	
    END TaskSwitch;

(************************************************************************)

PROCEDURE RunNextTask;

    (* Performs a task switch to the first task on the ready list	*)
    (* whose dynamic priority is CurrentPriority, if that task is not	*)
    (* blocked.  If it is blocked, we switch to its blocker instead.	*)

    VAR T: Task;  L: Lock;

    BEGIN
	T := ReadyList[CurrentPriority];
	LOOP
	    L := T^.WaitingFor;
	    IF (L = NIL) OR NOT L^.Locked THEN EXIT(*LOOP*) END(*IF*);
	    T := L^.Holder;
	END (*LOOP*);
	IF TimeSlicingEnabled THEN
	    T^.TimeLeft := timequota[T^.priority];
	END (*IF*);
	TaskSwitch (T);
    END RunNextTask;

(************************************************************************)

PROCEDURE SelectAnotherTask;

    (* Performs a task switch from the current task to the next ready	*)
    (* task.  It is assumed that the current task has already been	*)
    (* removed from the active list.  Notice that there is always at	*)
    (* least one ready task, namely the null task.			*)

    BEGIN
	WHILE ReadyList[CurrentPriority] = NIL DO
	    DEC (CurrentPriority);
	END (*WHILE*);
	RunNextTask;
    END SelectAnotherTask;

(************************************************************************)

PROCEDURE QueueAndSwitchTasks (VAR (*INOUT*) KQ: TaskQueue);

    (* Puts the current task on the tail of list KQ, and gives control	*)
    (* to the highest-priority ready task.				*)

    BEGIN
	IF CurrentTask^.FirstLock <> NIL THEN
	    Crash ("Task became inactive while holding a lock.");
	END (*IF*);
	LeaveQueue (ReadyList[CurrentTask^.priority]);
	AddToQueue (KQ, CurrentTask);
	SelectAnotherTask;
    END QueueAndSwitchTasks;

(************************************************************************)

PROCEDURE MarkAsReady (VAR (*INOUT*) FromQ: TaskQueue);

    (* Removes the first task from queue FromQ and makes it ready.  If	*)
    (* the task has a higher priority than the currently running task,	*)
    (* then we perform an immediate task switch.  Otherwise, the new	*)
    (* task descriptor is placed on the ready list.			*)
    (* Special case: if the incoming task is a sleeping task, it must	*)
    (* be a timing-out task which has been reactivated before its	*)
    (* timeout period has expired.  We run such a task immediately,	*)
    (* but don't yet put it on the list of active tasks.  The task runs	*)
    (* just far enough to clean up its kernel data structures, after	*)
    (* which procedure RecheckPriorities decides whether to switch back	*)
    (* to the preempted task or to legitimise the task switch.		*)

    VAR thispriority: PriorityLevel;
	T: Task;

    BEGIN
	T := TakeFromQueue (FromQ);
	IF T^.sleeping THEN
	    TaskSwitch (T);
	ELSE
	    thispriority := T^.priority;
	    AddToQueue (ReadyList[thispriority], T);
	    IF thispriority > CurrentPriority THEN
		CurrentPriority := thispriority;
		IF TimeSlicingEnabled THEN
		    T^.TimeLeft := timequota[thispriority];
		END (*IF*);
		TaskSwitch (T);
	    END (*IF*);
	END (*IF*);
    END MarkAsReady;

(************************************************************************)

PROCEDURE TimeSliceCheck;

    (* Called from the clock interrupt routine.  We check whether the	*)
    (* running task has used its time quota, and if so switch to the	*)
    (* next ready task.  Remark: it will sometimes happen that the	*)
    (* current task has used up its time quota, but that all ready	*)
    (* tasks have a lower priority than the current task.  In that	*)
    (* case, the end result of the "task switch" will be that the	*)
    (* current task gets its time quota refreshed and continues running.*)

    (* This procedure does nothing is time-slicing is disabled.		*)

    VAR savedPSW: CARDINAL;

    BEGIN
	IF TimeSlicingEnabled THEN
	    savedPSW := EnterKernel();
	    WITH CurrentTask^ DO
		DEC (TimeLeft);
		IF TimeLeft <= 0 THEN
		    ReadyList[priority] := ReadyList[priority]^.next;
		    RunNextTask;
		END (*IF*);
	    END (*WITH*);
	    LeaveKernel (savedPSW);
	END (*IF*);
    END TimeSliceCheck;

(************************************************************************)
(*			    TASK TERMINATION				*)
(************************************************************************)

PROCEDURE TaskExit;

    (* Removes the currently running task from the system, and performs	*)
    (* a task switch to the next ready task.				*)
    (* There is normally no need for a task to call this procedure,	*)
    (* because it is automatically called when the task code "falls out	*)
    (* the bottom" by executing its final procedure return.  The stack	*)
    (* is set up, at the time a task is created, in such a way that	*)
    (* TaskExit will be entered at that time.				*)

    VAR savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterKernel();
	WITH CurrentTask^ DO
	    IF FirstLock <> NIL THEN
		ReleaseAllLocks;
	    END (*IF*);

	    (* The following section is temporarily commented out,	*)
	    (* because what is needed is a little more complex than	*)
	    (* what is shown here.  This is just a crude first		*)
	    (* approximation to remind me to look at this issue later.	*)
	    (* If this code is run by an interrupt task then		*)
	    (* CurrentTask doesn't even identify its descriptor.	*)
	    (* The best solution might turn out to be to convert the	*)
	    (* interrupt task to a non-interrupt task (i.e. disconnect	*)
	    (* it from the interrupt mechanism) and then put it on the	*)
	    (* ready list with an arrangement to call the "normal" exit	*)
	    (* procedure when next it runs.				*)
(*
	    IF InterruptHandler THEN
		DisconnectFromInterrupt (selector);
		InterruptHandler := FALSE;
	    END (*IF*);
*)
	    dead := TRUE;
	    LeaveQueue (ReadyList[priority]);
	END (*WITH*);
	AddToQueue (UnusedDescriptors, CurrentTask);

	(* Note that the current task is now not on any of the active	*)
	(* kernel queues, and it never will be again.  Therefore, there	*)
	(* will never be a return from the SelectAnotherTask call below.*)
	(* This, plus the fact that we are still inside the kernel and	*)
	(* cannot be interrupted, justifies our deallocation of the	*)
	(* stack space even though the stack is about to be used to	*)
	(* push the return address for the SelectAnotherTask call.	*)
	(* (But this approach should perhaps be reviewed, since it does	*)
	(* rely on assumptions about how DEALLOCATE works.)		*)

	IF CurrentTask^.StackBase = NIL THEN
	    Crash ("TaskExit called from main program");
	ELSE
	    DEALLOCATE (CurrentTask^.StackBase, StackSize);
	    SelectAnotherTask;
	END (*IF*);

	(* We never reach this point.	*)

    END TaskExit;

(************************************************************************)
(*			    TASK INITIALISATION				*)
(************************************************************************)

PROCEDURE UsingFloatingPoint;

    (* Tells the kernel that this task is one which uses the floating	*)
    (* point processor.							*)

    BEGIN
	IF InterruptTaskActive THEN
	    Crash ("Interrupt task may not use floating point");
	END (*IF*);
	CurrentTask^.FloatingPointUser := TRUE;
    END UsingFloatingPoint;

(************************************************************************)

PROCEDURE NotUsingFloatingPoint;

    (* Tells the kernel that this task is one which does not use the	*)
    (* floating point processor.  Calling this (optional) procedure	*)
    (* speeds up task switching slightly, but it does put the onus on	*)
    (* the caller to be certain that no floating point operations will	*)
    (* be executed.							*)

    BEGIN
	CurrentTask^.FloatingPointUser := FALSE;
    END NotUsingFloatingPoint;

(************************************************************************)

PROCEDURE MakeTask (tptr: Task;  StartAddress: PROC;
			taskpriority: PriorityLevel;
			AllowInterrupts: BOOLEAN;  taskname: NameString);

    (* Initialises the stack for a new task, and fills in the fields of	*)
    (* its task descriptor tptr^.  The remaining parameters are the	*)
    (* start address of the task code; the priority of the new task;	*)
    (* a flag which is TRUE iff the task is to be started with		*)
    (* interrupts enabled; and a textual name for diagnostic purposes.	*)

    (* Remark:  An important function of this procedure is to set up	*)
    (* initial conditions for the task so that it looks as if it	*)
    (* entered the ready list in the same way as, for example, a	*)
    (* blocked task which has just become unblocked.  Procedure		*)
    (* InnerKernel.TaskInit looks after most of that detail.		*)

    BEGIN
	WITH tptr^ DO

	    (* Create the task stack, and give it the necessary		*)
	    (* initial contents.					*)

	    ALLOCATE (StackBase, StackSize);
	    selector := TaskInit (StackBase, StackSize, AllowInterrupts,
						TaskExit, StartAddress);
	    FPselector := MakeFloatSaveSelector (selector);

	    (* Set up the remaining task descriptor values.  Some of	*)
	    (* the assignments below are redundant, but I believe that	*)
	    (* filling in most of the fields is desirable in that it	*)
	    (* makes this module more readable and guards against	*)
	    (* strange bugs being introduced in future revisions.	*)

	    name := taskname;
	    priority := taskpriority;
	    WaitingFor := NIL;  FirstLock := NIL;
	    IF TimeSlicingEnabled THEN
		TimeLeft := timequota[priority];
	    ELSE
		TimeLeft := 0;
	    END (*IF*);
	    InterruptHandler := FALSE;  dead := FALSE;  sleeping := FALSE;
	    nextsleeper := NIL;
	    FloatingPointUser := TRUE;
	    InterruptNo := 0;

	    (* Save the creator's floating point state in the save area	*)
	    (* of the new task, to ensure that the new task starts with	*)
	    (* a meaningful floating point state.  Unfortunately the	*)
	    (* NPXsave seems to reset the floating point hardware	*)
	    (* to an undesired default, so we must restore the state	*)
	    (* again to get back where we were.				*)

	    NPXsave (FPselector);  NPXrestore (FPselector);

	END (*WITH*);

    END MakeTask;

(************************************************************************)

PROCEDURE CreateTask (StartAddress: PROC;  taskpriority: PriorityLevel;
						taskname: NameString);

    (* Must be called to introduce a task to the system. The first	*)
    (* parameter, which should be the name of a procedure containing	*)
    (* the task code, gives the starting address.  The second parameter	*)
    (* is the task's base priority.  If this task has a higher priority	*)
    (* than its creator, it will run immediately.  Otherwise, it	*)
    (* becomes ready.							*)
    (* The effective priority of a task can be higher than its base	*)
    (* priority, as the result of priority inheritance.  This happens	*)
    (* when the task holds a lock on which a higher-priority task is	*)
    (* waiting.								*)

    VAR tptr: Task;
	savedPSW: CARDINAL;
	
    BEGIN
	savedPSW := EnterKernel();
	tptr := UnusedDescriptors;
	IF tptr = NIL THEN
	    Crash ("Too many tasks created");
	END (*IF*);
	MakeTask (tptr, StartAddress, taskpriority, TRUE, taskname);
	tptr^.FloatingPointUser := TRUE;
	MarkAsReady (UnusedDescriptors);
	LeaveKernel (savedPSW);
    END CreateTask;

(************************************************************************)
(*		PROCEDURES TO DEAL WITH INTERRUPT TASKS			*)
(************************************************************************)

PROCEDURE CreateInterruptTask (InterruptNumber: CARDINAL;
				StartAddress: PROC;  taskname: NameString);

    (* Introduces an interrupt task to the system.  The first parameter	*)
    (* is the hardware-defined interrupt number, and the second is the	*)
    (* address of the procedure whose code is the interrupt handler.	*)
    (* An interrupt task differs from an ordinary task in that, when it	*)
    (* is not running, it is idle rather than ready, and the dispatcher	*)
    (* does not consider it to be among the tasks eligible to run.	*)
    (* (It is run by a task switch which is made directly by the	*)
    (* assembly language code which fields the interrupt).  When the	*)
    (* interrupt task has responded to the interrupt, it must call	*)
    (* procedure WaitForInterrupt to put itself back in the idle state.	*)
    (* On the next interrupt, it will continue from just after the call	*)
    (* to WaitForInterrupt.  Normally, therefore, the interrupt task	*)
    (* will be written as an infinite loop.  If for any reason the	*)
    (* interrupt task exits by falling out of the bottom of its code,	*)
    (* it will be destroyed in the same way as a normal task which	*)
    (* terminates.  That could be fatal, unless steps have been taken	*)
    (* to reset the interrupt vector.					*)

    VAR ITptr: Task;  savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterKernel();

	IF UnusedDescriptors = NIL THEN
	    Crash ("Too many tasks created");
	END (*IF*);

	ITptr := TakeFromQueue (UnusedDescriptors);
	MakeTask (ITptr, StartAddress, MAX(PriorityLevel), FALSE, taskname);
	WITH ITptr^ DO
	    InterruptHandler := TRUE;
	    FloatingPointUser := FALSE;
	    InterruptNo := InterruptNumber;
	END (*WITH*);

	(* Now start the interrupt task and connect it to the desired	*)
	(* interrupt.  This operation includes executing the task's	*)
	(* initialisation code, i.e. everything up to its first call to	*)
	(* procedure WaitForInterrupt.					*)

	InterruptTaskActive := TRUE;
	StartInterruptTask (ITptr^.selector, InterruptNumber);

	(* When we get back to here, the interrupt task has run to its	*)
	(* first call to WaitForInterrupt, and InterruptTaskActive is	*)
	(* FALSE again.							*)

	LeaveKernel (savedPSW);
	
    END CreateInterruptTask;

(************************************************************************)

PROCEDURE KillInterruptTask (InterruptNumber: CARDINAL);

    (* Removes an interrupt task from the system. *)

    VAR k: TaskNumber;  found: BOOLEAN;

    BEGIN
	k := MaxTaskNumber;
	REPEAT
	    WITH TaskTable[k] DO
		found := InterruptHandler AND NOT dead
				AND (InterruptNo = InterruptNumber);
	    END (*WITH*);
	    DEC (k);
	UNTIL found OR (k = 0);
	IF found THEN
	    INC (k);
	    WITH TaskTable[k] DO
		DisconnectFromInterrupt (selector);
		InterruptHandler := FALSE;  dead := TRUE;
		DEALLOCATE (StackBase, StackSize);
	    END (*WITH*);
	    AddToQueue (UnusedDescriptors, ADR(TaskTable[k]));
	END (*IF*);
    END KillInterruptTask;

(************************************************************************)

PROCEDURE WaitForInterrupt;

    (* Called by an interrupt task, to make itself dormant until the	*)
    (* next interrupt comes along.  It is not necessary to specify	*)
    (* the interrupt number, since this was fixed at the time the	*)
    (* interrupt task was created.					*)

    VAR savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterKernel();
	IF NOT InterruptTaskActive THEN
	    Crash ("Normal task has called WaitForInterrupt");
	END (*IF*);

	(* Procedure IOTransfer performs a task switch from the		*)
	(* interrupt task to the interrupted task, whose state was	*)
	(* saved at the time the interrupt occurred.			*)

	InterruptTaskActive := FALSE;
	IOTransfer;

	(* We return to this point after the next interrupt.	*)
	(* Return to the interrupt task.			*)
	
	InterruptTaskActive := TRUE;
	LeaveKernel (savedPSW);

    END WaitForInterrupt;

(************************************************************************)
(*			CREATING AND DESTROYING LOCKS			*)
(************************************************************************)

PROCEDURE CreateLock (VAR (*OUT*) L: Lock);

    (* Creates a new lock.	*)

    VAR savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterKernel();
	NEW (L);
	WITH L^ DO
	    Locked := FALSE;  Holder := NIL;
	    next := NIL;  previous := NIL;
	END (*WITH*);
	LeaveKernel (savedPSW);
    END CreateLock;

(************************************************************************)

PROCEDURE DestroyLock (VAR (*INOUT*) L: Lock);

    (* Disposes of a lock.	*)

    VAR savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterKernel();

	(* It is legitimate for a task to obtain L before destroying	*)
	(* it, in which case we must remove L from the list of locks	*)
	(* held by this task.  However we leave the Locked field set in	*)
	(* case some task still has a dangling pointer to this lock.	*)

	WITH L^ DO
	    IF Holder <> NIL THEN
		IF previous = NIL THEN Holder^.FirstLock := next
		ELSE previous^.next := next;
		END (*IF*);
		IF next <> NIL THEN
		next^.previous := previous;
		END (*IF*);
	    END (*IF*);
	    Locked := TRUE;
	END (*WITH*);
	DISPOSE (L);
	LeaveKernel (savedPSW);
    END DestroyLock;

(************************************************************************)
(*			     OBTAINING A LOCK				*)
(************************************************************************)

PROCEDURE Obtain (L: Lock);

    (* Obtains lock L, waiting if necessary.	*)

    VAR Blocker: Task;  M: Lock;
	savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterKernel();
	IF L^.Locked THEN

	    (* Work out the blocker of the current task.  Note: in an	*)
	    (* earlier version of this module I was able to avoid the	*)
	    (* loop below by keeping a BlockedBy field in each task	*)
	    (* descriptor; but it turned out to be more trouble than it	*)
	    (* was worth to update the BlockedBy information when a	*)
	    (* lock was released.  It's less expensive to incur the	*)
	    (* penalty here and keep the Release code simple.  In any	*)
	    (* case the penalty is not severe - it would take a really	*)
	    (* baroque example to slow us down here.			*)

	    M := L;
	    REPEAT
		Blocker := M^.Holder;  M := Blocker^.WaitingFor;
	    UNTIL (M = NIL) OR NOT M^.Locked;

	    IF Blocker = CurrentTask THEN
		Crash ("Deadlock detected");
	    END (*IF*);

	    CurrentTask^.WaitingFor := L;
	    IF TimeSlicingEnabled THEN
		Blocker^.TimeLeft := timequota[Blocker^.priority];
	    END (*IF*);
	    TaskSwitch (Blocker);

	    (* When execution resumes at this point (via	*)
	    (* RunNextTask), L^.Locked will be FALSE.		*)

	    CurrentTask^.WaitingFor := NIL;

	END (*IF*);

	WITH L^ DO
	    Locked := TRUE;  Holder := CurrentTask;

	    (* Add L to the list of locks held by CurrentTask. *)

	    previous := NIL;
	    next := CurrentTask^.FirstLock;
	    IF next <> NIL THEN
		next^.previous := L;
	    END (*IF*);
	    CurrentTask^.FirstLock := L;

	END (*WITH*);
	LeaveKernel (savedPSW);

    END Obtain;

(************************************************************************)
(*			     RELEASING A LOCK				*)
(************************************************************************)

PROCEDURE Release (L: Lock);

    (* Releases lock L.  This implicitly changes the "blocked by"	*)
    (* status of all tasks blocked by the current task, but it turns	*)
    (* out to be faster to get procedure RunNextTask to recheck the	*)
    (* blocking status than to keep track of "blocked by" information.	*)
    (* All we need to know here is whether we've inherited a priority	*)
    (* greater than our base priority.  If so, we must have been	*)
    (* blocking another task which should run now - we can trust	*)
    (* RunNextTask to work out the identity of that task.		*)

    VAR savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterKernel();
	WITH L^ DO

	    (* Remove L from the list of locks held by CurrentTask. *)

	    IF previous = NIL THEN CurrentTask^.FirstLock := next
	    ELSE previous^.next := next;
	    END (*IF*);
	    IF next <> NIL THEN
		next^.previous := previous;
	    END (*IF*);

	    (* Mark the lock as free. *)
	    Locked := FALSE;  Holder := NIL;

	END (*WITH*);

	(* If we have inherited an augmented priority, a task of higher	*)
	(* priority might just have become unblocked.			*)

	IF CurrentTask^.priority < CurrentPriority THEN
	    RunNextTask;
	END (*IF*);

	LeaveKernel (savedPSW);
    END Release;

(************************************************************************)

PROCEDURE ReleaseAllLocks;

    (* Releases all locks held by the current task.  Application-level	*)
    (* tasks normally won't need to call this procedure; it is		*)
    (* provided to support the system shutdown function and for things	*)
    (* like "emergency abort" operations.				*)

    VAR savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterKernel();
	WHILE CurrentTask^.FirstLock <> NIL DO
	    Release (CurrentTask^.FirstLock);
	END (*WHILE*);
	LeaveKernel (savedPSW);
    END ReleaseAllLocks;

(************************************************************************)
(*		PROCEDURES TO DEAL WITH SLEEPING TASKS			*)
(************************************************************************)

VAR SleepQueueHead: Task;

    (* SleepQueueHead points to a priority queue of descriptors for	*)
    (* tasks which are sleeping.  The first element of the list has the	*)
    (* time-to-go in its TimeLeft field.  For subsequent elements,	*)
    (* TimeLeft records the difference between its time-to-go and that	*)
    (* of the preceding list element.  There are some situations in	*)
    (* which the delay specification cannot be met exactly, so it is	*)
    (* possible for the TimeLeft field to go negative.			*)

(************************************************************************)
(*			SLEEP QUEUE INITIALISATION			*)
(************************************************************************)

PROCEDURE InitialiseSleepQueue;

    (* Creates an initially empty queue of sleeping tasks.	*)

    BEGIN
	SleepQueueHead := NIL;
    END InitialiseSleepQueue;

(************************************************************************)
(*			'PUT-ME-TO-SLEEP' PROCEDURES			*)
(************************************************************************)

PROCEDURE InsertCurrentTaskInSleepQueue (sleeptime: INTEGER);

    (* Inserts the current task into the sleep queue.  This queue is a	*)
    (* linear list.  The time to sleep is stored in incremental form:	*)
    (* for every task on the list except the first, the TimeLeft field	*)
    (* in the task descriptor holds the difference between the time to	*)
    (* go for this task and that of the task ahead of it in the list.	*)
    (* This means that the clock interrupt routine need only decrement	*)
    (* the TimeLeft field of the first sleeping task.			*)

    VAR previous, following: Task;

    BEGIN
	CurrentTask^.sleeping := TRUE;

	(* Find the correct place in the sleep queue. *)

	following := SleepQueueHead;  previous := NIL;
	LOOP
	    IF following = NIL THEN EXIT(*LOOP*) END (*IF*);
	    IF sleeptime < following^.TimeLeft THEN EXIT(*LOOP*)
	    END (*IF*);
	    previous := following;  following := following^.nextsleeper;
	    DEC (sleeptime, previous^.TimeLeft);
	END (*LOOP*);

	(* Insert the task descriptor for this task between	*)
	(* previous^ and following^.				*)

	IF previous = NIL THEN
	    SleepQueueHead := CurrentTask;
	ELSE
	    previous^.nextsleeper := CurrentTask;
	END (*IF*);
	CurrentTask^.nextsleeper := following;

	(* Fix the TimeLeft fields for the current and following tasks.	*)

	CurrentTask^.TimeLeft := sleeptime;
	IF following <> NIL THEN
	    DEC (following^.TimeLeft, sleeptime);
	END (*IF*);

    END InsertCurrentTaskInSleepQueue;

(************************************************************************)

PROCEDURE RemoveCurrentFromSleepQueue;

    (* Removes the current task from the sleep queue.	*)

    VAR previous, current: Task;

    BEGIN
	previous := NIL;  current := SleepQueueHead;
	WHILE current <> CurrentTask DO
	    previous := current;  current := current^.nextsleeper;
	END (*WHILE*);
	IF current^.nextsleeper <> NIL THEN
	    INC (current^.nextsleeper^.TimeLeft, current^.TimeLeft);
	END (*IF*);
	IF previous = NIL THEN
	    SleepQueueHead := current^.nextsleeper;
	ELSE
	    previous^.nextsleeper := current^.nextsleeper;
	END (*IF*);
	CurrentTask^.sleeping := FALSE;
    END RemoveCurrentFromSleepQueue;

(************************************************************************)

PROCEDURE RecheckPriorities;

    (* This procedure is called, just before leaving the kernel, by a	*)
    (* sleeping task which has been woken up.  The task is running but	*)
    (* has not yet been put on the list of active tasks.  If this task	*)
    (* has a high enough priority, we put it on the active list and let	*)
    (* it continue to run.  Otherwise, we mark it as ready but switch	*)
    (* back to the task which it preempted.				*)

    VAR thispriority: PriorityLevel;

    BEGIN
	thispriority := CurrentTask^.priority;
	IF thispriority > CurrentPriority THEN
	    AddToQueue (ReadyList[thispriority], CurrentTask);
	    CurrentPriority := thispriority;
	    IF TimeSlicingEnabled THEN
		CurrentTask^.TimeLeft := timequota[thispriority];
	    ELSE
		CurrentTask^.TimeLeft := 0;
	    END (*IF*);
	ELSE
	    AddToQueue (ReadyList[thispriority], CurrentTask);
	    TaskSwitch (LastTask);
	END (*IF*);
    END RecheckPriorities;

(************************************************************************)

PROCEDURE Delay (sleeptime: INTEGER);

    (* Puts the calling task to sleep for approximately the specified	*)
    (* number of clock ticks.  The time delay is necessarily		*)
    (* approximate, because (a) the clock might tick over when we are	*)
    (* in the middle of this procedure; (b) after the expiration of the	*)
    (* delay period, the sleeping task is made ready, but this does not	*)
    (* guarantee that it will run immediately.				*)

    VAR savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterKernel();
	IF CurrentTask^.FirstLock <> NIL THEN
	    Crash ("Task sleeping while holding a lock");
	END (*IF*);
	LeaveQueue (ReadyList[CurrentTask^.priority]);
	InsertCurrentTaskInSleepQueue (sleeptime);
	SelectAnotherTask;

	(* We resume at this point after waking up.		*)

	RecheckPriorities;
	LeaveKernel (savedPSW);

    END Delay;

(************************************************************************)
(*		    CLOCK TICK HANDLER FOR SLEEPING TASKS		*)
(************************************************************************)

PROCEDURE CheckSleepers;

    (* Called from the clock interrupt task.  We check whether a	*)
    (* sleeping task needs to be woken up, and wake it up if so.	*)

    (* Note: this procedure typically needs no explicit critical	*)
    (* section protection.  It is called from the clock interrupt	*)
    (* handler, therefore runs with interrupts disabled.  We prefer,	*)
    (* however, to stick to the rule that all kernel procedures are	*)
    (* protected via EnterKernel and LeaveKernel, since it makes the	*)
    (* kernel easier to check for correctness.  This convention also	*)
    (* helps portability.  It means, for example, that this procedure	*)
    (* will still work in the multiprocessor case, or in the case where	*)
    (* the clock interrupt routine does not disable all interrupts.	*)

    VAR	first, second: Task;
	savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterKernel();
	first := SleepQueueHead;
	IF first <> NIL THEN

	    (* update sleeping time of head of sleep queue *)

	    DEC (first^.TimeLeft);

	    (* Is it time to wake up first sleeping task? *)

	    IF first^.TimeLeft <= 0 THEN

		(* Yes.  Correct sleeping time of second sleeper. *)

		second := first^.nextsleeper;
		SleepQueueHead := second;
		IF second <> NIL THEN
		    INC (second^.TimeLeft, first^.TimeLeft);
		END (* IF second <> NIL *);

		(* Wake up the first sleeping task.  Because of		*)
		(* complications when this task is still on a semaphore	*)
		(* blocked list, we must run the task immediately	*)
		(* rather than simply make it ready.  The task is not	*)
		(* yet put on the list of active tasks, because it	*)
		(* might be preempting a task of higher priority.	*)
		(* Before the newly awakened task leaves the kernel, it	*)
		(* will call procedure RecheckPriorities to clean up	*)
		(* such details.					*)

		first^.sleeping := FALSE;
		TaskSwitch (first);

	    END (* IF sleeping time has expired *)

	END (* IF sleep queue not empty *);

	LeaveKernel (savedPSW);

    END CheckSleepers;

(************************************************************************)
(*			TASK QUEUEING WITH TIMEOUT			*)
(************************************************************************)

PROCEDURE QueueWithTimeout (VAR (*INOUT*) KQ: TaskQueue;
					TimeLimit: INTEGER): BOOLEAN;

    (* Like procedure QueueAndSwitchTasks, this procedure puts the	*)
    (* current task on the tail of list KQ, and gives control to the	*)
    (* highest-priority ready task.  The difference is that we allow	*)
    (* this task to remain on KQ for at most TimeLimit timer ticks.	*)
    (* If the task is removed from KQ before the time limit expires,	*)
    (* we return a result of FALSE.  If the time limit expires first,	*)
    (* we remove the queued task from KQ anyway, and make it runnable,	*)
    (* and return a result of TRUE when it does run.			*)
    (* Note: this procedure may be called only from inside the kernel.	*)
    (* While being timed out, a task is simultaneously on two kernel	*)
    (* queues: the specified KQ, and the Sleep queue.  It will be	*)
    (* removed from one of these - which one depends, of course, on	*)
    (* whether the time limit expires - by some other kernel action,	*)
    (* and at that stage we must remove it from the other queue before	*)
    (* it leaves the kernel.  If we did not guarantee indivisibility	*)
    (* of the operation of removing the task from the two queues, we	*)
    (* would run the risk of putting the same task onto the ready	*)
    (* structure twice.							*)

    VAR TimedOut: BOOLEAN;

    BEGIN
	InsertCurrentTaskInSleepQueue (TimeLimit);
	QueueAndSwitchTasks (KQ);

	(* When execution of this task resumes at this point, we have	*)
	(* been activated either by being removed from KQ or by a timer	*)
	(* event (but never both simultaneously).  That is, we have	*)
	(* been removed from one queue, and must now remove ourself	*)
	(* from the other.						*)

	TimedOut := NOT CurrentTask^.sleeping;
	IF TimedOut THEN
	    LeaveQueue (KQ);
	ELSE
	    RemoveCurrentFromSleepQueue;
	END (*IF*);

	(* Additional complication: the kernel procedures have so far	*)
	(* given the newly awakened task preferential treatment, to	*)
	(* allow it to remove itself from both queues before leaving	*)
	(* the kernel.  If this has caused a higher-priority task to be	*)
	(* preempted, we must give the processor back.			*)

	RecheckPriorities;
	RETURN TimedOut;

    END QueueWithTimeout;

(************************************************************************)
(*				PMOS SHUTDOWN				*)
(************************************************************************)

PROCEDURE ShutDown;

    (* Kills all remaining interrupt tasks, and partially inhibits	*)
    (* multitasking by giving the current task - which is running the	*)
    (* program termination code - the maximum possible priority.  This	*)
    (* still allows the possibility of a task switch caused by		*)
    (* something like a semaphore Wait; but it does ensure that the	*)
    (* mere unblocking of another task, for example by releasing a	*)
    (* Lock, will not cause a task switch.  Notice too that time	*)
    (* slicing is irrelevant beyond this point, because the timer	*)
    (* interrupt task will be disabled.					*)

    VAR k: TaskNumber;  savedPSW: CARDINAL;

    BEGIN
	savedPSW := EnterKernel();

	(* Remove any interrupt handlers which might still be active.	*)
	(* The reason for doing this backwards is that this is more	*)
	(* likely to work in the case of multiple handlers for the	*)
	(* same interrupt number.  At some time I should look for a	*)
	(* more robust solution for this case.				*)

	FOR k := MaxTaskNumber TO 0 BY -1 DO
	    WITH TaskTable[k] DO
		IF InterruptHandler AND NOT dead THEN
		    DisconnectFromInterrupt (selector);
		    InterruptHandler := FALSE;  dead := TRUE;
		    DEALLOCATE (StackBase, StackSize);
		    AddToQueue (UnusedDescriptors, ADR(TaskTable[k]));
		END (*IF*);
	    END (*WITH*);
	END (*FOR*);

	(* Increase the priority of the current task. *)

	LeaveQueue (ReadyList[CurrentTask^.priority]);
	CurrentPriority := MAX(PriorityLevel);
	CurrentTask^.priority := CurrentPriority;
	AddToQueue (ReadyList[CurrentPriority], CurrentTask);
	LeaveKernel (savedPSW);

    END ShutDown;

(************************************************************************)
(*			    MODULE INITIALISATION			*)
(************************************************************************)

PROCEDURE InitialiseTaskControl;

    (* Must be called before any tasks are created, to initialise the	*)
    (* task control data structures.					*)

    VAR p: PriorityLevel;  j: TaskNumber;
	k: InterruptType;
	quota: CARDINAL;

    BEGIN
	InterruptTaskActive := FALSE;

	(* Initialise the timequota array. *)

	IF TimeSlicingEnabled THEN
	    quota := 1;
	    FOR p := MaxPriority TO 1 BY -1 DO
		timequota[p] := quota;  quota := 2*quota;
	    END (*FOR*);
	    timequota[0] := 32767;
	END (*IF*);

	(* Collect all the unused task descriptors into a linear list.	*)

	CreateQueue (UnusedDescriptors);
	FOR j := 0 TO MaxTaskNumber DO
	    TaskTable[j].dead := TRUE;
	    TaskTable[j].sleeping := FALSE;
	    AddToQueue (UnusedDescriptors, ADR (TaskTable[j]) );
	END (*FOR*);

	(* The active list and the sleep queue are initially empty.  *)

	FOR p := 0 TO MaxPriority DO
	    CreateQueue (ReadyList[p]);
	END (*FOR*);
	InitialiseSleepQueue;

	(* Create a descriptor for the current task, i.e. for the task	*)
	(* which called this procedure.  We give it a low priority	*)
	(* because this task is often running the background jobs.  We	*)
	(* also set its StackBase to NIL because the stack for this	*)
	(* task already exists, i.e. it's not created by this module.	*)
	
	LastTask := NIL;
	CurrentTask := TakeFromQueue (UnusedDescriptors);
	CurrentPriority := 1;
	WITH CurrentTask^ DO
	    name := "Main program";
	    priority := CurrentPriority;
	    WaitingFor := NIL;  FirstLock := NIL;
	    FPselector := MakeFloatSaveSelector (InitMainTask());
	    StackBase := NIL;
	    InterruptHandler := FALSE;  dead := FALSE;
	    FloatingPointUser := TRUE;  sleeping := FALSE;
	    InterruptNo := 0;
	END (*WITH*);
	AddToQueue (ReadyList[CurrentPriority], CurrentTask);
	
	(* Create the null task.  It will not run immediately, because	*)
	(* the current (initialisation) task has higher priority.	*)

	CreateTask (NullTask, 0, "Null task");

	(* We have a working operating system.  It is now safe to	*)
	(* enable clock interrupts, and to call CreateTask to introduce	*)
	(* more tasks to the system.  Device interrupts can be enabled	*)
	(* when the device drivers are installed.			*)

    END InitialiseTaskControl;

(************************************************************************)
(*			    INITIALISATION				*)
(************************************************************************)

BEGIN
    InitialiseTaskControl;
    SetTerminationProcedure (ShutDown);
END TaskControl.