| (* Executive.mod provides a simple multitasking executive. |
| |
| Copyright (C) 2002-2025 Free Software Foundation, Inc. |
| Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>. |
| |
| This file is part of GNU Modula-2. |
| |
| GNU Modula-2 is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GNU Modula-2 is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| Under Section 7 of GPL version 3, you are granted additional |
| permissions described in the GCC Runtime Library Exception, version |
| 3.1, as published by the Free Software Foundation. |
| |
| You should have received a copy of the GNU General Public License and |
| a copy of the GCC Runtime Library Exception along with this program; |
| see the files COPYING3 and COPYING.RUNTIME respectively. If not, see |
| <http://www.gnu.org/licenses/>. *) |
| |
| IMPLEMENTATION MODULE Executive[MAX(PROTECTION)] ; |
| |
| FROM SYSTEM IMPORT ADDRESS, PROCESS, LISTEN, ADR, |
| NEWPROCESS, TRANSFER, IOTRANSFER, ListenLoop, |
| TurnInterrupts ; |
| |
| FROM COROUTINES IMPORT PROTECTION ; |
| FROM SysStorage IMPORT ALLOCATE, DEALLOCATE ; |
| FROM StrLib IMPORT StrCopy ; |
| FROM StrLib IMPORT StrLen ; |
| FROM NumberIO IMPORT CardToStr ; |
| FROM Debug IMPORT DebugString, Halt ; |
| |
| |
| (* IMPORT gdb ; *) |
| |
| |
| CONST |
| MaxCharsInName = 15 ; |
| IdleStackSize = 16 * 1024 * 1024 ; |
| |
| TYPE |
| SEMAPHORE = POINTER TO Semaphore ; (* defines dijkstra's semaphores *) |
| Semaphore = RECORD |
| Value : CARDINAL ; (* semaphore value *) |
| SemName: EntityName ; (* semaphore name for debugging *) |
| Who : DESCRIPTOR ; (* queue of waiting processes *) |
| ExistsQ: SemQueue ; (* list of existing semaphores *) |
| END ; |
| |
| DESCRIPTOR= POINTER TO Descriptor ; (* handle onto a process *) |
| Descriptor= RECORD |
| Volatiles : PROCESS ; (* process volatile environment *) |
| ReadyQ : DesQueue ; (* queue of ready processes *) |
| ExistsQ : DesQueue ; (* queue of existing processes *) |
| SemaphoreQ : DesQueue ; (* queue of waiting processes *) |
| Which : SEMAPHORE ; (* which semaphore are we waiting*) |
| RunName : EntityName ; (* process name for debugging *) |
| Status : State ; (* state of process *) |
| RunPriority: Priority ; (* runtime priority of process *) |
| Size : CARDINAL ; (* Maximum stack size *) |
| Start : ADDRESS ; (* Stack start *) |
| Debugged : BOOLEAN ; (* Does user want to debug a *) |
| (* deadlocked process? *) |
| END ; |
| |
| DesQueue = RECORD |
| Right, |
| Left : DESCRIPTOR ; |
| END ; |
| |
| SemQueue = RECORD |
| Right, |
| Left : SEMAPHORE ; |
| END ; |
| |
| EntityName= ARRAY [0..MaxCharsInName] OF CHAR ; |
| |
| Priority = (idle, lo, hi) ; (* process run priority *) |
| |
| State = (Runnable, Suspended, WaitOnSem, WaitOnInt) ; |
| |
| VAR |
| ExistsQueue : DESCRIPTOR ; (* List of existing processes *) |
| RunQueue : ARRAY Priority OF DESCRIPTOR ; |
| (* List of runnable processes *) |
| CurrentProcess: DESCRIPTOR ; |
| AllSemaphores : SEMAPHORE ; (* List of all semaphores *) |
| GarbageItem : DESCRIPTOR ; (* Descriptor destined to free *) |
| |
| |
| (* |
| Assert - |
| *) |
| |
| PROCEDURE Assert (c: BOOLEAN; file: ARRAY OF CHAR; line: CARDINAL; |
| function: ARRAY OF CHAR) ; |
| BEGIN |
| IF NOT c |
| THEN |
| Ps ; |
| Halt ('assert failed', file, function, line) |
| END |
| END Assert ; |
| |
| |
| (* |
| InitProcess - initializes a process which is held in the suspended |
| state. When the process is resumed it will start executing |
| procedure, p. The process has a maximum stack size of, |
| StackSize, bytes and its textual name is, Name. |
| The StackSize should be at least 5000 bytes. |
| *) |
| |
| PROCEDURE InitProcess (p: PROC; |
| StackSize: CARDINAL; |
| Name: ARRAY OF CHAR) : DESCRIPTOR ; |
| VAR |
| d : DESCRIPTOR ; |
| ToOldState: PROTECTION ; |
| db : ARRAY [0..80] OF CHAR ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *) |
| NEW(d) ; |
| WITH d^ DO |
| Size := StackSize ; |
| (* allocate space for this processes stack *) |
| ALLOCATE(Start, StackSize) ; |
| NEWPROCESS(p, Start, StackSize, Volatiles) ; (* create volatiles *) |
| InitQueue(ReadyQ) ; (* not on the ready queue as suspended *) |
| AddToExists(d) ; (* add process to the exists queue *) |
| InitQueue(SemaphoreQ) ; (* not on a semaphore queue yet *) |
| Which := NIL ; (* not on a semaphore queue yet *) |
| StrCopy(Name, RunName) ; (* copy name into descriptor for debugging *) |
| Status := Suspended ; (* this process will be suspended *) |
| RunPriority := lo ; (* all processes start off at lo priority *) |
| Debugged := FALSE ; (* no need to debug deadlock yet! *) |
| END ; |
| (* ToOldState := TurnInterrupts(ToOldState) ; (* restore interrupts *) *) |
| RETURN( d ) (* and return a descriptor to the caller *) |
| END InitProcess ; |
| |
| |
| (* |
| KillProcess - kills the current process. Notice that if InitProcess |
| is called again, it might reuse the DESCRIPTOR of the |
| killed process. It is the responsibility of the caller |
| to ensure all other processes understand this process |
| is different. |
| *) |
| |
| PROCEDURE KillProcess ; |
| VAR |
| ToOldState: PROTECTION ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *) |
| SubFromReady(CurrentProcess) ; |
| SubFromExists(ExistsQueue, CurrentProcess) ; |
| GarbageItem := CurrentProcess ; |
| Reschedule ; |
| (* ToOldState := TurnInterrupts(ToOldState) (* restore interrupts *) *) |
| END KillProcess ; |
| |
| |
| (* |
| Resume - resumes a suspended process. If all is successful then the process, p, |
| is returned. If it fails then NIL is returned. |
| *) |
| |
| PROCEDURE Resume (d: DESCRIPTOR) : DESCRIPTOR ; |
| VAR |
| ToOldState: PROTECTION ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *) |
| WITH d^ DO |
| IF Status=Suspended |
| THEN |
| (* legal state transition *) |
| Status := Runnable ; (* change status *) |
| AddToReady(d) ; (* add to run queue *) |
| RunQueue[RunPriority] := d ; (* make d at top of q *) |
| Reschedule (* check whether this process has a higher run priority *) |
| ELSE |
| (* we are trying to Resume a process which is *) |
| Halt ('trying to resume a process which is not suspended', |
| __FILE__, __FUNCTION__, __LINE__) ; |
| RETURN( NIL ) (* not held in a Suspended state - error *) |
| END |
| END ; |
| (* ToOldState := TurnInterrupts(ToOldState) ; (* restore interrupts *) *) |
| RETURN( d ) |
| END Resume ; |
| |
| |
| (* |
| Suspend - suspend the calling process. |
| The process can only continue running if another process |
| Resumes it. |
| *) |
| |
| PROCEDURE Suspend ; |
| VAR |
| ToOldState: PROTECTION ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *) |
| WITH CurrentProcess^ DO |
| Status := Suspended |
| END ; |
| SubFromReady(CurrentProcess) ; |
| Reschedule ; |
| (* ToOldState := TurnInterrupts(ToOldState) (* restore interrupts *) *) |
| END Suspend ; |
| |
| |
| (* |
| InitSemaphore - creates a semaphore whose initial value is, v, and |
| whose name is, Name. |
| *) |
| |
| PROCEDURE InitSemaphore (v: CARDINAL; Name: ARRAY OF CHAR) : SEMAPHORE ; |
| VAR |
| s : SEMAPHORE ; |
| ToOldState: PROTECTION ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *) |
| NEW(s) ; |
| WITH s^ DO |
| Value := v ; (* initial value of semaphore *) |
| StrCopy(Name, SemName) ; (* save the name for future debugging *) |
| Who := NIL ; (* no one waiting on this semaphore yet *) |
| AddToSemaphoreExists(s) ; (* add semaphore to exists list *) |
| END ; |
| (* ToOldState := TurnInterrupts(ToOldState) ; (* restore interrupts *) *) |
| RETURN( s ) |
| END InitSemaphore ; |
| |
| |
| (* |
| Wait - performs dijkstra's P operation on a semaphore. |
| A process which calls this procedure will |
| wait until the value of the semaphore is > 0 |
| and then it will decrement this value. |
| *) |
| |
| PROCEDURE Wait (s: SEMAPHORE) ; |
| VAR |
| ToOldState: PROTECTION ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *) |
| WITH s^ DO |
| IF Value>0 |
| THEN |
| DEC( Value ) |
| ELSE |
| SubFromReady(CurrentProcess) ; (* remove from run q *) |
| IF Who=CurrentProcess |
| THEN |
| Ps ; |
| Halt ('we are already on sem', |
| __FILE__, __FUNCTION__, __LINE__) |
| END ; |
| AddToSemaphore(Who, CurrentProcess) ; (* add to semaphore q *) |
| CurrentProcess^.Status := WaitOnSem ; (* set new status *) |
| CurrentProcess^.Which := s ; (* debugging aid *) |
| Reschedule (* find next process *) |
| END |
| END ; |
| (* ToOldState := TurnInterrupts(ToOldState) (* restore interrupts *) *) |
| END Wait ; |
| |
| |
| (* |
| Signal - performs dijkstra's V operation on a semaphore. |
| A process which calls the procedure will increment |
| the semaphores value. |
| *) |
| |
| PROCEDURE Signal (s: SEMAPHORE) ; |
| VAR |
| ToOldState: PROTECTION ; |
| d : DESCRIPTOR ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *) |
| WITH s^ DO |
| IF Who=NIL |
| THEN |
| INC( Value ) (* no process waiting *) |
| ELSE |
| d := SubFromSemaphoreTop(Who) ; (* remove process from semaphore q *) |
| d^.Which := NIL ; (* no longer waiting on semaphore *) |
| d^.Status := Runnable ; (* set new status *) |
| AddToReady(d) ; (* add process to the run queue *) |
| Reschedule (* find out whether there is a *) |
| (* higher priority to run. *) |
| END |
| END ; |
| (* ToOldState := TurnInterrupts(ToOldState) (* restore interrupts *) *) |
| END Signal ; |
| |
| |
| (* |
| WaitForIO - waits for an interrupt to occur on vector, VectorNo. |
| *) |
| |
| PROCEDURE WaitForIO (VectorNo: CARDINAL) ; |
| VAR |
| Calling : DESCRIPTOR ; |
| Next : PROCESS ; |
| ToOldState: PROTECTION ; |
| r : INTEGER ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; *) |
| (* |
| DebugString('inside WaitForIO ') ; |
| DebugString(CurrentProcess^.RunName) ; |
| DebugString('\n') ; |
| *) |
| Assert(CurrentProcess^.Status=Runnable, |
| __FILE__, __LINE__, __FUNCTION__) ; |
| SubFromReady(CurrentProcess) ; (* remove process from run queue *) |
| (* |
| alter run priority to hi as all processes waiting for an interrupt |
| are scheduled to run at the highest priority. |
| *) |
| WITH CurrentProcess^ DO |
| Status := WaitOnInt ; (* it will be blocked waiting for an interrupt. *) |
| RunPriority := hi ; (* this (hopefully) allows it to run as soon as *) |
| (* the interrupt occurs. *) |
| END ; |
| Calling := CurrentProcess ; (* process which called WaitForIO *) |
| CurrentProcess := NextReady() ; (* find next process to run while we wait *) |
| Next := CurrentProcess^.Volatiles ; |
| (* |
| This is quite complicated. We transfer control to the next process saving |
| our volatile environment into the Calling process descriptor volatiles. |
| When an interrupt occurs the calling process will be resumed and the |
| interrupted process volatiles will be placed into Next. |
| *) |
| IOTRANSFER(Calling^.Volatiles, Next, VectorNo) ; |
| |
| (* |
| At this point the interrupt has just occurred and the volatiles of |
| the interrupted process are in Next. Next is the current process |
| and so we must save them before picking up the Calling descriptor. |
| *) |
| |
| CurrentProcess^.Volatiles := Next ; (* carefully stored away *) |
| CurrentProcess := Calling ; (* update CurrentProcess *) |
| (* |
| DebugString(CurrentProcess^.RunName) ; |
| *) |
| CurrentProcess^.Status := Runnable ; (* add to run queue *) |
| AddToReady(CurrentProcess) ; |
| (* |
| DebugString(' finishing WaitForIO\n') ; |
| *) |
| |
| (* ToOldState := TurnInterrupts(ToOldState) (* restore interrupts *) *) |
| END WaitForIO ; |
| |
| |
| (* |
| Ps - displays a process list together with relevant their status. |
| *) |
| |
| PROCEDURE Ps ; |
| VAR |
| ToOldState: PROTECTION ; |
| p : DESCRIPTOR ; |
| s : SEMAPHORE ; |
| a : ARRAY [0..5] OF CHAR ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *) |
| p := ExistsQueue ; |
| IF p#NIL |
| THEN |
| REPEAT |
| DisplayProcess(p) ; |
| p := p^.ExistsQ.Right |
| UNTIL p=ExistsQueue |
| END ; |
| s := AllSemaphores ; |
| IF s#NIL |
| THEN |
| REPEAT |
| WITH s^ DO |
| DebugString(SemName) ; |
| WriteNSpaces(MaxCharsInName-StrLen(SemName)) ; |
| CardToStr(Value, 0, a) ; |
| DebugString(a) ; |
| DebugString('\n') |
| END ; |
| s := s^.ExistsQ.Right |
| UNTIL s=AllSemaphores |
| END ; |
| (* ToOldState := TurnInterrupts(ToOldState) (* restore interrupts *) *) |
| END Ps ; |
| |
| |
| (* |
| DisplayProcess - displays the process, p, together with its status. |
| *) |
| |
| PROCEDURE DisplayProcess (p: DESCRIPTOR) ; |
| VAR |
| a: ARRAY [0..4] OF CHAR ; |
| BEGIN |
| WITH p^ DO |
| DebugString(RunName) ; WriteNSpaces(MaxCharsInName-StrLen(RunName)) ; |
| CASE RunPriority OF |
| |
| idle: DebugString(' idle ') | |
| lo : DebugString(' lo ') | |
| hi : DebugString(' hi ') |
| |
| END ; |
| CASE Status OF |
| |
| Runnable : DebugString('runnable ') | |
| Suspended: DebugString('suspended') | |
| WaitOnSem: DebugString('waitonsem (') ; |
| DebugString(Which^.SemName) ; |
| DebugString(')') | |
| WaitOnInt: DebugString('waitonint') |
| |
| END ; |
| DebugString('\n') |
| END |
| END DisplayProcess ; |
| |
| |
| (* |
| WriteNSpaces - writes, n, spaces. |
| *) |
| |
| PROCEDURE WriteNSpaces (n: CARDINAL) ; |
| BEGIN |
| WHILE n>0 DO |
| DebugString(' ') ; |
| DEC(n) |
| END |
| END WriteNSpaces ; |
| |
| |
| (* |
| GetCurrentProcess - returns the descriptor of the current running |
| process. |
| *) |
| |
| PROCEDURE GetCurrentProcess () : DESCRIPTOR ; |
| VAR |
| ToOldState: PROTECTION ; |
| p : DESCRIPTOR ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *) |
| p := CurrentProcess ; |
| (* ToOldState := TurnInterrupts(ToOldState) ; (* restore interrupts *) *) |
| RETURN( p ) |
| END GetCurrentProcess ; |
| |
| |
| (* |
| RotateRunQueue - rotates the process run queue. |
| *) |
| |
| PROCEDURE RotateRunQueue ; |
| VAR |
| ToOldState: PROTECTION ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *) |
| (* we only need to rotate the lo priority processes as: |
| idle - should only have one process (the idle process) |
| hi - are the device drivers which most of the time are performing |
| WaitForIO |
| *) |
| IF RunQueue[lo]#NIL |
| THEN |
| RunQueue[lo] := RunQueue[lo]^.ReadyQ.Right |
| END ; |
| (* ToOldState := TurnInterrupts(ToOldState) (* restore interrupts *) *) |
| END RotateRunQueue ; |
| |
| |
| (* |
| ProcessName - displays the name of process, d, through |
| DebugString. |
| *) |
| |
| PROCEDURE ProcessName (d: DESCRIPTOR) ; |
| BEGIN |
| DebugString(d^.RunName) |
| END ProcessName ; |
| |
| |
| (* |
| DebugProcess - |
| *) |
| |
| PROCEDURE DebugProcess (d: DESCRIPTOR) ; |
| VAR |
| ToOldState: PROTECTION ; |
| BEGIN |
| (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; *) |
| WITH d^ DO |
| IF Status=WaitOnSem |
| THEN |
| DebugString('debugging process (') ; |
| DebugString(RunName) ; |
| DebugString(') was waiting on semaphore (') ; |
| DebugString(Which^.SemName) ; |
| DebugString(')\n') ; |
| SubFromSemaphore(Which^.Who, d) ; |
| AddToReady(d) ; |
| Status := Runnable ; |
| Debugged := TRUE ; |
| Reschedule |
| ELSE |
| DebugString('can only debug deadlocked processes (') ; |
| DebugString(RunName) ; |
| DebugString(') which are waiting on a semaphore\n') |
| END |
| END ; |
| (* ToOldState := TurnInterrupts(ToOldState) *) |
| END DebugProcess ; |
| |
| |
| (* |
| CheckDebugged - checks to see whether the debugged flag has |
| been set by the debugger. |
| TRUE is returned if the process was debugged. |
| FALSE is returned if the process was not debugged. |
| *) |
| |
| PROCEDURE CheckDebugged () : BOOLEAN ; |
| BEGIN |
| WITH CurrentProcess^ DO |
| IF Debugged |
| THEN |
| (* |
| You will see this comment after you have enabled a |
| deadlocked process to continue via the gdb command: |
| |
| print Executive_DebugProcess(d) |
| |
| debugger caused deadlocked process to continue |
| *) |
| (* gdb.breakpoint ; *) |
| Debugged := FALSE ; |
| SubFromReady(CurrentProcess) ; |
| AddToSemaphore(Which^.Who, CurrentProcess) ; |
| (* add it back to the queue sem *) |
| Status := WaitOnSem ; |
| |
| RETURN( TRUE ) |
| END |
| END ; |
| RETURN( FALSE ) |
| END CheckDebugged ; |
| |
| |
| (* |
| Reschedule - reschedules to the highest runnable process. |
| *) |
| |
| PROCEDURE Reschedule ; |
| BEGIN |
| (* |
| the repeat loop allows us to debug a process even when it is |
| technically waiting on a semaphore. We run the process into |
| a breakpoint and then back into this schedule routine. |
| This is really useful when trying to find out why processes have |
| deadlocked. |
| *) |
| REPEAT |
| ScheduleProcess |
| UNTIL NOT CheckDebugged() |
| END Reschedule ; |
| |
| |
| (* |
| ScheduleProcess - finds the highest priority Runnable process and |
| then transfers control to it. |
| *) |
| |
| PROCEDURE ScheduleProcess ; |
| VAR |
| From, |
| Highest: DESCRIPTOR ; |
| BEGIN |
| Highest := NextReady() ; |
| |
| (* rotate ready Q to ensure fairness *) |
| RunQueue[Highest^.RunPriority] := Highest^.ReadyQ.Right ; |
| |
| (* no need to transfer if Highest=CurrentProcess *) |
| IF Highest#CurrentProcess |
| THEN |
| From := CurrentProcess ; |
| (* |
| DebugString('context switching from ') ; DebugString(From^.RunName) ; |
| *) |
| (* alter CurrentProcess before we TRANSFER *) |
| CurrentProcess := Highest ; |
| (* |
| DebugString(' to ') ; DebugString(CurrentProcess^.RunName) ; |
| *) |
| |
| TRANSFER(From^.Volatiles, Highest^.Volatiles) ; |
| (* |
| ; DebugString(' (') ; DebugString(CurrentProcess^.RunName) ; |
| DebugString(')\n') ; |
| *) |
| CheckGarbageCollect |
| END |
| END ScheduleProcess ; |
| |
| |
| (* |
| NextReady - returns the highest priority Runnable process. |
| *) |
| |
| PROCEDURE NextReady () : DESCRIPTOR ; |
| VAR |
| Highest: DESCRIPTOR ; |
| Pri : Priority ; |
| BEGIN |
| Highest := NIL ; |
| FOR Pri := idle TO hi DO |
| IF RunQueue[Pri]#NIL |
| THEN |
| Highest := RunQueue[Pri] |
| END |
| END ; |
| Assert(Highest#NIL, __FILE__, __LINE__, __FUNCTION__) ; |
| RETURN( Highest ) |
| END NextReady ; |
| |
| |
| (* |
| CheckGarbageCollect - checks to see whether GarbageItem is set |
| and if so it deallocates storage associated |
| with this descriptor. |
| *) |
| |
| PROCEDURE CheckGarbageCollect ; |
| BEGIN |
| IF GarbageItem#NIL |
| THEN |
| WITH GarbageItem^ DO |
| DEALLOCATE(Start, Size) |
| END ; |
| DISPOSE(GarbageItem) ; |
| GarbageItem := NIL |
| END |
| END CheckGarbageCollect ; |
| |
| |
| (* |
| AddToExists - adds item, Item, to the exists queue. |
| *) |
| |
| PROCEDURE AddToExists (Item: DESCRIPTOR) ; |
| BEGIN |
| IF ExistsQueue=NIL |
| THEN |
| ExistsQueue := Item ; (* Head is empty therefore make *) |
| Item^.ExistsQ.Left := Item ; (* Item the only entry on this *) |
| Item^.ExistsQ.Right := Item (* queue. *) |
| ELSE |
| Item^.ExistsQ.Right := ExistsQueue ; (* Add Item to the end of queue *) |
| Item^.ExistsQ.Left := ExistsQueue^.ExistsQ.Left ; |
| ExistsQueue^.ExistsQ.Left^.ExistsQ.Right := Item ; |
| ExistsQueue^.ExistsQ.Left := Item |
| END |
| END AddToExists ; |
| |
| |
| (* |
| SubFromExists - removes a process, Item, from the exists queue, Head. |
| *) |
| |
| PROCEDURE SubFromExists (VAR Head: DESCRIPTOR; Item: DESCRIPTOR) ; |
| BEGIN |
| IF (Item^.ExistsQ.Right=Head) AND (Item=Head) |
| THEN |
| Head := NIL |
| ELSE |
| IF Head=Item |
| THEN |
| Head := Head^.ExistsQ.Right |
| END ; |
| Item^.ExistsQ.Left^.ExistsQ.Right := Item^.ExistsQ.Right ; |
| Item^.ExistsQ.Right^.ExistsQ.Left := Item^.ExistsQ.Left |
| END |
| END SubFromExists ; |
| |
| |
| (* |
| AddToSemaphore - adds item, Item, to the semaphore queue defined by Head. |
| *) |
| |
| PROCEDURE AddToSemaphore (VAR Head: DESCRIPTOR; Item: DESCRIPTOR) ; |
| BEGIN |
| IF Head=NIL |
| THEN |
| Head := Item ; (* Head is empty therefore make *) |
| Item^.SemaphoreQ.Left := Item ; (* Item the only entry on this *) |
| Item^.SemaphoreQ.Right := Item (* queue. *) |
| ELSE |
| Item^.SemaphoreQ.Right := Head ; (* Add Item to the end of queue *) |
| Item^.SemaphoreQ.Left := Head^.SemaphoreQ.Left ; |
| Head^.SemaphoreQ.Left^.SemaphoreQ.Right := Item ; |
| Head^.SemaphoreQ.Left := Item |
| END |
| END AddToSemaphore ; |
| |
| |
| (* |
| AddToSemaphoreExists - adds item, Item, to the semaphore exists queue. |
| *) |
| |
| PROCEDURE AddToSemaphoreExists (Item: SEMAPHORE) ; |
| BEGIN |
| IF AllSemaphores=NIL |
| THEN |
| AllSemaphores := Item ; (* Head is empty therefore make *) |
| Item^.ExistsQ.Left := Item ; (* Item the only entry on this *) |
| Item^.ExistsQ.Right := Item (* queue. *) |
| ELSE |
| Item^.ExistsQ.Right := AllSemaphores ; |
| (* Add Item to the end of queue *) |
| Item^.ExistsQ.Left := AllSemaphores^.ExistsQ.Left ; |
| AllSemaphores^.ExistsQ.Left^.ExistsQ.Right := Item ; |
| AllSemaphores^.ExistsQ.Left := Item |
| END |
| END AddToSemaphoreExists ; |
| |
| |
| (* |
| AddToReady - adds item, Item, to the ready queue. |
| *) |
| |
| PROCEDURE AddToReady (Item: DESCRIPTOR) ; |
| BEGIN |
| AddToReadyQ(RunQueue[Item^.RunPriority], Item) |
| END AddToReady ; |
| |
| |
| (* |
| AddToReadyQ - adds item, Item, to the ready queue defined by Head. |
| *) |
| |
| PROCEDURE AddToReadyQ (VAR Head: DESCRIPTOR; Item: DESCRIPTOR) ; |
| BEGIN |
| IF Head=NIL |
| THEN |
| Head := Item ; (* Head is empty therefore make *) |
| Item^.ReadyQ.Left := Item ; (* Item the only entry on this *) |
| Item^.ReadyQ.Right := Item (* queue. *) |
| ELSE |
| Item^.ReadyQ.Right := Head ; (* Add Item to the end of queue *) |
| Item^.ReadyQ.Left := Head^.ReadyQ.Left ; |
| Head^.ReadyQ.Left^.ReadyQ.Right := Item ; |
| Head^.ReadyQ.Left := Item |
| END |
| END AddToReadyQ ; |
| |
| |
| (* |
| SubFromReady - subtract process descriptor, Item, from the Ready queue. |
| *) |
| |
| PROCEDURE SubFromReady (Item: DESCRIPTOR) ; |
| BEGIN |
| SubFromReadyQ(RunQueue[Item^.RunPriority], Item) |
| END SubFromReady ; |
| |
| |
| (* |
| SubFromReadyQ - removes a process, Item, from a queue, Head. |
| *) |
| |
| PROCEDURE SubFromReadyQ (VAR Head: DESCRIPTOR; Item: DESCRIPTOR) ; |
| BEGIN |
| IF (Item^.ReadyQ.Right=Head) AND (Item=Head) |
| THEN |
| Head := NIL |
| ELSE |
| IF Head=Item |
| THEN |
| Head := Head^.ReadyQ.Right |
| END ; |
| Item^.ReadyQ.Left^.ReadyQ.Right := Item^.ReadyQ.Right ; |
| Item^.ReadyQ.Right^.ReadyQ.Left := Item^.ReadyQ.Left |
| END |
| END SubFromReadyQ ; |
| |
| |
| (* |
| SubFromSemaphoreTop - returns the first descriptor in the |
| semaphore queue. |
| *) |
| |
| PROCEDURE SubFromSemaphoreTop (VAR Head: DESCRIPTOR) : DESCRIPTOR ; |
| VAR |
| Top: DESCRIPTOR ; |
| BEGIN |
| Top := Head ; |
| SubFromSemaphore(Head, Top) ; |
| RETURN( Top ) |
| END SubFromSemaphoreTop ; |
| |
| |
| (* |
| SubFromSemaphore - removes a process, Item, from a queue, Head. |
| *) |
| |
| PROCEDURE SubFromSemaphore (VAR Head: DESCRIPTOR; Item: DESCRIPTOR) ; |
| BEGIN |
| IF (Item^.SemaphoreQ.Right=Head) AND (Item=Head) |
| THEN |
| Head := NIL |
| ELSE |
| IF Head=Item |
| THEN |
| Head := Head^.SemaphoreQ.Right |
| END ; |
| Item^.SemaphoreQ.Left^.SemaphoreQ.Right := Item^.SemaphoreQ.Right ; |
| Item^.SemaphoreQ.Right^.SemaphoreQ.Left := Item^.SemaphoreQ.Left |
| END |
| END SubFromSemaphore ; |
| |
| |
| (* |
| Idle - this process is only run whenever there is no other Runnable |
| process. It should never be removed from the run queue. |
| *) |
| |
| PROCEDURE Idle ; |
| VAR |
| ToOldState: PROTECTION ; |
| BEGIN |
| ToOldState := TurnInterrupts(MIN(PROTECTION)) ; (* enable interrupts *) |
| LOOP |
| (* |
| Listen for interrupts. |
| We could solve chess endgames here or calculate PI etc. |
| We forever wait for an interrupt since there is nothing else |
| to do... |
| *) |
| ListenLoop |
| END |
| (* we must NEVER exit from the above loop *) |
| END Idle ; |
| |
| |
| (* |
| InitIdleProcess - creates an idle process descriptor which |
| is run whenever no other process is Runnable. |
| The Idle process should be the only process which |
| has the priority idle. |
| *) |
| |
| VAR |
| IdleProcess: DESCRIPTOR ; (* Idle process always runnable *) |
| |
| PROCEDURE InitIdleProcess ; |
| VAR |
| db : ARRAY [0..80] OF CHAR ; |
| BEGIN |
| NEW(IdleProcess) ; |
| WITH IdleProcess^ DO |
| ALLOCATE(Start, IdleStackSize) ; |
| Size := IdleStackSize ; |
| NEWPROCESS(Idle, Start, IdleStackSize, Volatiles) ; |
| InitQueue(SemaphoreQ) ; (* not on a semaphore queue *) |
| Which := NIL ; (* at all. *) |
| StrCopy('Idle', RunName) ; (* idle process's name *) |
| Status := Runnable ; (* should always be idle *) |
| RunPriority := idle ; (* lowest priority possible *) |
| Debugged := FALSE ; (* should never be debugging *) |
| END ; |
| AddToReady(IdleProcess) ; (* should be the only *) |
| (* process at this run priority *) |
| AddToExists(IdleProcess) (* process now exists.. *) |
| END InitIdleProcess ; |
| |
| |
| (* |
| InitInitProcess - creates a descriptor for this running environment |
| so it too can be manipulated by Reschedule. |
| |
| This concept is important to understand. |
| InitInitProcess is called by the startup code to this |
| module. It ensures that the current stack and processor |
| volatiles can be "housed" in a process descriptor and |
| therefore it can be manipulated just like any other |
| process. |
| *) |
| |
| PROCEDURE InitInitProcess ; |
| BEGIN |
| NEW(CurrentProcess) ; |
| WITH CurrentProcess^ DO |
| Size := 0 ; (* we dont know the size of main stack *) |
| Start := NIL ; (* we don't need to know where it is. *) |
| InitQueue(ReadyQ) ; (* assign queues to NIL *) |
| InitQueue(ExistsQ) ; |
| InitQueue(SemaphoreQ) ; (* not waiting on a semaphore queue yet *) |
| Which := NIL ; (* at all. *) |
| StrCopy('Init', RunName) ; (* name for debugging purposes *) |
| Status := Runnable ; (* currently running *) |
| RunPriority := lo ; (* default status *) |
| Debugged := FALSE ; (* not deadlock debugging yet *) |
| END ; |
| AddToExists(CurrentProcess) ; |
| AddToReady(CurrentProcess) |
| END InitInitProcess ; |
| |
| |
| (* |
| InitQueue - initializes a queue, q, to empty. |
| *) |
| |
| PROCEDURE InitQueue (VAR q: DesQueue) ; |
| BEGIN |
| WITH q DO |
| Right := NIL ; |
| Left := NIL |
| END |
| END InitQueue ; |
| |
| |
| (* |
| Init - initializes all the global variables. |
| *) |
| |
| PROCEDURE Init ; |
| BEGIN |
| ExistsQueue := NIL ; |
| RunQueue[lo] := NIL ; |
| RunQueue[hi] := NIL ; |
| RunQueue[idle] := NIL ; |
| AllSemaphores := NIL ; |
| GarbageItem := NIL ; |
| InitInitProcess ; |
| InitIdleProcess |
| END Init ; |
| |
| |
| BEGIN |
| Init |
| END Executive. |