packages feed

language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/unsorted/minikern.mod

IMPLEMENTATION MODULE MiniKernel;

	(****************************************************************)
	(*								*)
	(*	A stripped-down demonstration version of an operating	*)
	(*			system kernel.				*)
	(*								*)
	(*	Programmer:	P. Moylan				*)
	(*	Last edited:	16 August 1992				*)
	(*	Status:		OK					*)
	(*		Have had to disable kernel tracing, since it	*)
	(*		causes infinite recursion.  In future must	*)
	(*		find a new mechanism for handling this.		*)
	(*								*)
	(****************************************************************)

FROM SYSTEM IMPORT
    (* type *)	ADDRESS,
    (* proc *)	ADR;

FROM SYSTEM IMPORT
    (* proc *)	NEWPROCESS, TRANSFER;

FROM Storage IMPORT
    (* proc *)	ALLOCATE, DEALLOCATE;
(*
FROM GlassTTY IMPORT
    (* proc *)	WriteString, WriteLn;
*)
(*
FROM Trace IMPORT
    (* proc *)	InTrace, OutTrace;
*)

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

CONST
    MaxTaskNumber = 31;
    MaxPriority = 3;
    StackSize = 1024;

TYPE
    TaskNumber = [0..MaxTaskNumber];
    PriorityType = [0..MaxPriority];
    TaskPointer = POINTER TO TaskDescriptor;

	(****************************************************************)
	(*								*)
	(* Each task in the system has a task descriptor.		*)
	(* The fields in a task descriptor are:				*)
	(*	next:	pointer to next task descriptor on the queue.	*)
	(*		(i.e. whatever system queue this task		*)
	(*		descriptor happens to be on).			*)
	(*	priority: task priority.				*)
	(*	selector: a selector for the task state of this task.	*)
	(*	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.				*)
	(*								*)
	(****************************************************************)

    TaskDescriptor = RECORD
			next		: TaskPointer;
			priority	: PriorityType;
			selector	: ADDRESS;
			StackBase	: ADDRESS;
		     END;

	(* The system maintains a number of queues.  Each of these has	*)
	(* a header which points to the first and last elements on the	*)
	(* queue.  For an empty queue, both pointers are NIL.		*)

    KernelQueue =   RECORD
			head, tail: TaskPointer;
		    END;

    Semaphore = POINTER TO RECORD
				value	:	INTEGER;
				blockedlist :	KernelQueue
			   END;

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

VAR

    TaskTable: ARRAY TaskNumber OF TaskDescriptor;

	(* 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 the UnusedDescriptors	*)
	(* list, available for the creation of new tasks.  A task	*)
	(* descriptor is allocated by procedure CreateTask, and is	*)
	(* returned to UnusedDescriptors by procedure TaskExit.		*)

    CurrentTaskPointer: TaskPointer;

	(* CurrentTaskPointer points to the running task.  Note: the	*)
	(* currently running task is not considered to be "ready", and	*)
	(* its task descriptor is not on any of the kernel queues.	*)

    UnusedDescriptors: KernelQueue;

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

    ReadyList: ARRAY PriorityType OF KernelQueue;

	(* ReadyList holds task descriptors for all the ready tasks.	*)
	(* It is an array of linear lists, one list for each priority	*)
	(* level.  Priority level 0 is special: only the null task has	*)
	(* priority zero, and the null task is always ready except when	*)
	(* it is running.						*)

(************************************************************************)
(*			    KERNEL LIST OPERATIONS			*)
(************************************************************************)

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

    (* Creates an initially empty kernel queue.	*)

    BEGIN
	KQ.head := NIL;  KQ.tail := NIL;
    END CreateQueue;

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

PROCEDURE AddToList (VAR (*INOUT*) Q: KernelQueue; TaskPtr: TaskPointer);

    (* Adds the task descriptor pointed to by TaskPtr to the tail of	*)
    (* queue Q.								*)

    BEGIN
	TaskPtr^.next := NIL;
	IF Q.head = NIL THEN Q.head := TaskPtr
	ELSE Q.tail^.next := TaskPtr
	END (*IF*);
	Q.tail := TaskPtr;
    END AddToList;

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

PROCEDURE TakeFromList (VAR (*INOUT*) Q: KernelQueue): TaskPointer;

    (* Removes the first entry, which is a pointer to a task		*)
    (* descriptor, from queue Q and returns it.  Assumption: the caller	*)
    (* has already verified that the queue is nonempty.			*)

    VAR result: TaskPointer;

    BEGIN
	result := Q.head;
	Q.head := result^.next;
	IF Q.head = NIL THEN Q.tail := NIL  END (*IF*);
	RETURN result;
    END TakeFromList;

(************************************************************************)
(*				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
	(*InTrace ("NullTask");*)
	LOOP (* Do nothing *) END (*LOOP*);
    END NullTask;

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

PROCEDURE TaskSwitch (T: TaskPointer);

    (* 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.					*)

    VAR OldTaskPointer: TaskPointer;

    BEGIN
	(*InTrace ("TaskSwitch");*)
	IF CurrentTaskPointer <> T THEN
	    OldTaskPointer := CurrentTaskPointer;  CurrentTaskPointer := T;
	    TRANSFER (OldTaskPointer^.selector, CurrentTaskPointer^.selector);
	END (*IF*);
	(*OutTrace ("TaskSwitch");*)
    END TaskSwitch;

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

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 put	*)
    (* on the appropriate kernel queue.  Notice that there is always at	*)
    (* least one ready task, namely the null task.  The only time that	*)
    (* the ready structure can become totally empty is when the null	*)
    (* task is running.  In that case, this procedure will be called	*)
    (* only after the null task has been put back on the ready list.	*)

    VAR p: PriorityType;

    BEGIN
	(*InTrace ("SelectAnotherTask");*)
	p := MaxPriority;
	WHILE ReadyList[p].head = NIL DO
	    DEC (p);
	END (*WHILE*);
	TaskSwitch (TakeFromList (ReadyList[p]));
	(*OutTrace ("SelectAnotherTask");*)
    END SelectAnotherTask;

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

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

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

    BEGIN
	(*InTrace ("QueueAndSwitchTasks");*)
	AddToList (KQ, CurrentTaskPointer);
	SelectAnotherTask;
	(*OutTrace ("QueueAndSwitchTasks");*)
    END QueueAndSwitchTasks;

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

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

    (* 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.			*)

    VAR thispriority, currentpriority: PriorityType;
	T: TaskPointer;

    BEGIN
	(*InTrace ("MarkAsReady");*)
	currentpriority := CurrentTaskPointer^.priority;
	T := TakeFromList (FromQ);
	thispriority := T^.priority;
	IF (thispriority > currentpriority) THEN
	    AddToList (ReadyList[currentpriority], CurrentTaskPointer);
	    TaskSwitch (T);
	ELSE AddToList (ReadyList[thispriority], T);
	END (*IF*);
	(*OutTrace ("MarkAsReady");*)
    END MarkAsReady;

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

PROCEDURE TaskExit;

    (* Removes the currently running task from the system, and performs	*)
    (* a task switch to the next ready task.				*)

    BEGIN
	(*InTrace ("TaskExit");*)
	AddToList (UnusedDescriptors, CurrentTaskPointer);

	(* 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.*)

	DEALLOCATE (CurrentTaskPointer^.StackBase, StackSize);
	SelectAnotherTask;

	(* We never reach this point.	*)

    END TaskExit;

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

PROCEDURE CreateTask (StartAddress: PROC; taskpriority: CARDINAL);

    (* 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 priority.  If this task has a higher priority than	*)
    (* its creator, it will run immediately.  Otherwise, it becomes	*)
    (* ready.								*)

    BEGIN
	(*InTrace ("CreateTask");*)
	WITH UnusedDescriptors.head^ DO
	    priority := taskpriority;
	    ALLOCATE (StackBase, StackSize);
	    NEWPROCESS (StartAddress, StackBase, StackSize, selector);
	END (*WITH*);
	MarkAsReady (UnusedDescriptors);
	(*OutTrace ("CreateTask");*)
    END CreateTask;

(************************************************************************)
(*				SEMAPHORES				*)
(************************************************************************)

PROCEDURE CreateSemaphore (VAR (*OUT*) s: Semaphore; InitialValue: INTEGER);

    (* Creates semaphore s, with the given initial value and an empty	*)
    (* queue.								*)

    BEGIN
	(*InTrace ("CreateSemaphore");*)
	NEW(s);
	s^.value := InitialValue;
	CreateQueue (s^.blockedlist);
	(*OutTrace ("CreateSemaphore");*)
    END CreateSemaphore;

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

PROCEDURE Wait (VAR (*INOUT*) s: Semaphore);

    (* Decrements the semaphore value.  If the value goes negative,	*)
    (* the calling task is blocked and there is a task switch.		*)

    BEGIN
	(*InTrace ("Wait");*)
	DEC (s^.value);
	IF s^.value < 0 THEN
	    QueueAndSwitchTasks (s^.blockedlist)
	END (*IF*);
	(*OutTrace ("Wait");*)
    END Wait;

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

PROCEDURE Signal (VAR (*INOUT*) s: Semaphore);

    (* Increments the semaphore value.  Unblocks one waiting task,	*)
    (* if there was one.						*)

    BEGIN
	(*InTrace ("Signal");*)
	INC (s^.value);
	IF s^.value <= 0 THEN

	    (* Note: the test (s^.value <= 0) is equivalent to	*)
	    (* the test (s^.blockedlist.head <> NIL).		*)

	    MarkAsReady (s^.blockedlist);

	END (*IF*);
	(*OutTrace ("Signal");*)
    END Signal;

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

PROCEDURE InitialiseTaskControl;

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

    VAR p: PriorityType;  j: TaskNumber;

    BEGIN
	(*InTrace ("InitialiseTaskControl");*)

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

	CreateQueue (UnusedDescriptors);
	FOR j := 0 TO MaxTaskNumber DO
	    AddToList (UnusedDescriptors, ADR (TaskTable[j]) );
	END (*FOR*);

	(* The ready list structure is initially empty.  *)

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

	(* 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.	*)

	CurrentTaskPointer := TakeFromList (UnusedDescriptors);
	CurrentTaskPointer^.priority := 1;

	(* Create the null task.  It will not run immediately, because	*)
	(* the current (initialisation) task has higher priority.	*)

	CreateTask (NullTask, 0);

	(* We have a working operating system.  It is now safe to	*)
	(* call CreateTask to introduce more tasks to the system.	*)

	(*OutTrace ("InitialiseTaskControl");*)
    END InitialiseTaskControl;

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

BEGIN
    InitialiseTaskControl;
END MiniKernel.