Trailing-Edge
-
PDP-10 Archives
-
T10_DECMAIL_MS_V11_FT1_860414
-
10,7/mail/mx/nmuskd.bli
There are 3 other files named nmuskd.bli in the archive. Click here to see a list.
module NMUSKD ( ! Scheduler
ident = 'X00.22',
main = TOPMOST,
version = '4(117)',
environment (stack = TOPMOST_STACK)
) =
begin
!
! COPYRIGHT (C) 1981 BY
! DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASSACHUSETTS 01754
!
! THIS SOFTWARE IS FURNISHED UNDER A LICENSE FOR USE ONLY ON A SINGLE
! COMPUTER SYSTEM AND MAY BE COPIED ONLY WITH THE INCLUSION OF THE
! ABOVE COPYRIGHT NOTICE. THIS SOFTWARE, OR ANY OTHER COPIES THEREOF
! MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY OTHER PERSON
! EXCEPT FOR USE ON SUCH SYSTEM AND TO ONE WHO AGREES TO THESE LICENSE
! TERMS. TITLE TO AND OWNERSHIP OF THE SOFTWARE SHALL AT ALL TIMES
! REMAIN IN DEC.
!
! THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE
! AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT
! CORPORATION.
!
! DEC ASSUMES NO RESPONSIBILITY FOR THE USE OR RELIABILITY OF ITS
! SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DEC.
!
!++
! Facility: LSG DECnet Network Management
!
! Abstract:
!
! This module provides the scheduling code for multi-tasking.
! The main routine (start) for any program using this scheduler
! is contained here also.
!
! Scheduling is done on two entities:
!
! (1) Queues when a queue becomes non-empty all
! tasks waiting are scheduled.
!
! (2) Events when an event occurs the first task waiting for
! the event is scheduled.
!
!
! Environment: TOPS10 and TOPS20 user mode, MCB RSX task mode
!
! Author: Steven M. Jenness, Creation date: 20 August 1980
!
!--
!
! Include files
!
library 'NMULIB'; ! All needed definitions
%if $TOPS20
%then
library 'MONSYM'; ! Monitor symbols
library 'JLNKG'; ! JSYS linkage definitions
%fi
!
! Global routines
!
forward routine
NMU$SCHED_MANAGER; ! Global entry definitions
!
! Local routines
!
forward routine
TOPMOST : novalue, ! Starting routine
GET_NEXT_TASK, ! Empty RUN_QUEUE
MAKE_TASK_RUNNABLE : novalue, ! Fill up RUN_QUEUE
SCHEDULER_TASK : novalue, ! Scheduler task co-routine
TIMEOUT_SIGNAL : TIMER_INTERRUPT_LINKAGE novalue, ! Signal SKD_EVENT
TT_SCAN; ! Scan TIME_QUEUE
!
! Global literals
!
global literal
%name ('TB.SKD') = %fieldexpand (TB_SCHED_QUEUE, 0) * %UPVAL,
%name ('TB.QUE') = %fieldexpand (TB_TASK_QUEUE, 0) * %UPVAL,
%name ('TB.STR') = %fieldexpand (TB_START, 0) * %UPVAL,
%name ('TB.ABT') = %fieldexpand (TB_ABORT, 0) * %UPVAL,
%name ('TB.RSC') = %fieldexpand (TB_RESOURCE, 0) * %UPVAL,
%name ('TB.ERR') = %fieldexpand (TB_ERROR_PC, 0) * %UPVAL,
%name ('TB.NAM') = %fieldexpand (TB_NAME, 0) * %UPVAL,
%if $TOPS10 or $TOPS20
%then
%name ('TB.BUF') = %fieldexpand (TB_ERROR_BUFFER, 0) * %UPVAL,
%fi
%name ('TB.EVW') = %fieldexpand (TB_EVENT_WAIT, 0) * %UPVAL,
%name ('TB.SEM') = %fieldexpand (TB_SEMAPHORE, 0) * %UPVAL,
%name ('TB.TIM') = %fieldexpand (TB_TIME, 0) * %UPVAL,
%name ('TB.CTX') = %fieldexpand (TB_CONTEXT, 0) * %UPVAL,
%name ('TB.STK') = %fieldexpand (TB_STACK, 0) * %UPVAL;
!
! Own storage
!
!
! Make something have an initial value so $OWN$ PSECT for NMUSKD
! shows up in TKB36 map.
!
literal
STACK_LENGTH = 600,
RUN_QUEUE_SIZE = 50; ! Max number of concurrently running
! tasks
own
COPYRIGHT : vector [11] initial (%asciz 'COPYRIGHT (C) DIGITAL EQUIPMENT CORPORATION, 1984.'),
TOPMOST_STACK : vector [STACK_LENGTH], ! Use this stack
RUN_QUEUE : vector [RUN_QUEUE_SIZE],! Queue of runnable tasks
NEXT_RUNNABLE_TASK : initial (-1), ! Points into RUN_QUEUE
LAST_RUNNABLE_TASK : initial (-1), ! Last runnable task pointer
TASK_QUEUE : Q_HEADER, ! Queue of all tasks
SKD_EVENT : EVENT_BLOCK, ! Event for scheduler wakeup
SKD_TASK : ref TASK_BLOCK, ! Task block for scheduler
TIME_OUT : TIME_BLOCK;
global
TIME_QUEUE : Q_HEADER, ! Queue of tasks waiting for timeouts
NMLVER : initial (4),
DECECO : initial (0),
USRECO : initial (0);
%master_debug_data_base; ! Debugging data base
!
! Structures
!
! Timer queue scanning data block
!
$field
TQUEUE_SCAN_FIELDS =
set
TT_NOW = [$sub_block (TIME_BLOCK_SIZE)], ! Current time
TT_TIME = [$sub_block (TIME_BLOCK_SIZE)] ! Least of wakeup times found
tes;
literal
TQUEUE_SCAN_SIZE = $field_set_size;
macro
TIMER_SCAN_BLOCK = block [TQUEUE_SCAN_SIZE] field (TQUEUE_SCAN_FIELDS) %;
!
! External references
!
external
CURTSK; ! Current task pointer
external routine
NMU$TABLE_ROUTINES, ! Table manipulation routines
NMU$QUEUE_MANAGER, ! Queue manager routines
NMU$MEMORY_MANAGER, ! Memory management routines
INIT_GLOBALS; ! Other global routines for
! initialization
%routine ('TOPMOST') : novalue =
!++
! Functional description:
!
! This is the "main" routine for the program using this
! scheduler.
!
! It initializes the memory management system, scheduler and
! any other system that requires initialization (defined in
! NMINI.REQ).
!
!
! Note that interlocks need to be developed to allow multi-streaming
! of initialization code.
!
!--
begin
!
! Detach the job from the Force Line if necessary. (Tops-10 only)
!
%if $TOPS10 %then DETACH (); %fi
!
! Setup the debugging data base (if needed)
!
%debug_setup;
!
! Reset memory manager
!
NMU$MEMORY_RESET ();
NMU$MEMORY_INITIALIZE (BASIC_MEMORY);
!
! Reset the scheduler.
!
! Reset master task queue and queue of runnable tasks.
!
NMU$QUEUE_RESET (TASK_QUEUE);
!
! Create null task using the current stack.
!
NMU$QUEUE_RESET (TIME_QUEUE);
NMU$SCHED_EVENT (SKD_EVENT, $true);
SKD_TASK = NMU$MEMORY_GET (TASK_BLOCK_ALLOCATION);
CURTSK = .SKD_TASK;
begin
local
TASK_NAME_POINTER, NAME_LITERAL;
TASK_NAME_POINTER = ch$ptr (SKD_TASK [TB_NAME]);
NAME_LITERAL = ch$asciz ('SCHEDULER');
ch$movasciz (TASK_NAME_POINTER, .NAME_LITERAL);
end;
!
! Initialize all other systems.
!
MASTER_INITIALIZATION;
!
! Start scheduling by calling scheduler co-routine
!
SCHEDULER_TASK ();
!
! If return here ... NML has crashed.
!
end; ! End of TOPMOST
%global_routine ('NMU$SCHED_CREATE', CODE, STACK_SIZE, ABORT_RTN, NAME_PTR) =
!++
! Functional description:
!
! This routine allocates a task state block and sets the task
! up for execution. The code specified is started when the
! task comes up in the runnable task queue.
!
! Formal parameters:
!
! .CODE Address of routine to execute as the task
! .STACK_SIZE Number of fullwords to allocate for stack
! .ABORT_RTN Address of a routine to call when the task
! is aborted
! .NAME_PTR Byte pointer to a ASCIZ task name string
!
! Routine value:
!
! The returned value is the address of the task's state
! block. This value is used whenever the task is to
! be affected by such routines as NMU$SCHED_ABORT.
!
! Side effects:
!
! The runnable task queue gets another task.
!
!--
begin
local
TASK : ref TASK_BLOCK,
TASK_NAME;
%debug (SCHEDULER_TRACE, (TRACE_INFO ('Creating %A (%O)', .NAME_PTR, .CODE)));
%if $TOPS10 or $TOPS20
%then
STACK_SIZE = .STACK_SIZE*2;
%fi
!
! Get memory for a task block
!
TASK = NMU$MEMORY_GET (TASK_BLOCK_ALLOCATION + (.STACK_SIZE*%upval));
!
! Initialize start address, error routine, task name and
! stack.
!
TASK [TB_START] = .CODE;
TASK_INITIALIZE (.TASK, .STACK_SIZE);
TASK [TB_ABORT] = .ABORT_RTN;
TASK_NAME = ch$ptr (TASK [TB_NAME]);
ch$movasciz (TASK_NAME, .NAME_PTR);
!
! Insert entry into master task queue and
! runnable task queue.
!
NMU$QUEUE_INSERT (TASK_QUEUE, TASK [TB_TASK_QUEUE]);
MAKE_TASK_RUNNABLE (TASK [TB_SCHED_QUEUE]);
!
! Return address of the task block.
!
.TASK
end; ! End of NMU$SCHED_CREATE
%global_routine ('NMU$SCHED_ABORT', TASK : ref TASK_BLOCK) : novalue =
!++
! Functional description:
!
! This routine aborts a task and calls it's clean up routine.
!
! Formal parameters:
!
! .TASK Address of task block for task to abort.
!
! Routine value: none
! Side effects:
!
! The specified task will be aborted, it's cleanup routine
! will be called (if it exists) and all traces of the task
! will disappear.
!
!--
begin
bind routine
A_ROUTINE = .TASK [TB_ABORT]; ! Abort routine address
%debug (SCHEDULER_TRACE, (TRACE_INFO ('Aborting task %A (%O)',
ch$ptr (TASK [TB_NAME]), .TASK)));
if A_ROUTINE neq 0 then A_ROUTINE (.TASK);
!*****
! If task is completely aborted ... delete the task and resources
!*****
end; ! End of NMU$SCHED_ABORT
%global_routine ('NMU$SCHED_FINI') : novalue =
!++
! Functional description:
!
! This routine is called whenever a task "exits". This means
! that the routine comprising the main routine of the task
! attempts to return.
!
! Formal parameters: none
!
! Routine value: none
! Side effects:
!
!--
begin
TASK_INFO ('Task has exited');
NMU$SCHED_DESCHEDULE ();
end; ! End of NMU$SCHED_FINI
%global_routine ('NMU$SCHED_ERROR', ERROR_PTR) : novalue =
!++
! Functional description:
!
! This routine is called in a task's context to abnormally
! complete it's execution. It puts the supplied error message
! into the task's task block error buffer and then calls
! NMU$SCHED_COMPLETE to abort the task.
!
! Formal parameters:
!
! .ERROR_PTR Pointer to ASCIZ string to be put into the
! task block error buffer.
!
! Routine value: none
! Side effects: none
!
!--
begin
%if not $MCB
%then
local
TASK : ref TASK_BLOCK,
TB_POINTER;
TASK = CURRENT_TASK;
TB_POINTER = ch$ptr (TASK [TB_ERROR_BUFFER]);
if .ERROR_PTR neq 0
then ch$movasciz (TB_POINTER, .ERROR_PTR);
%fi
NMU$SCHED_COMPLETE ();
end; ! End of NMU$SCHED_ERROR
%global_routine ('NMU$SCHED_COMPLETE') : novalue =
!++
! Functional description:
!
! This routine is called in a task's context to abnormally
! complete it's execution. It outputs the message that is
! stored in the task block's error buffer, aborts the task
! and deschedules it forever.
!
! Formal parameters: none
!
! Routine value: none
! Side effects: none
!
!--
begin
local
TASK : ref TASK_BLOCK;
TASK = CURRENT_TASK;
%if not $MCB
%then
TASK_INFO (ch$ptr (TASK [TB_ERROR_BUFFER]));
%fi
%if $TOPS10 %then STOP_PROGRAM; %fi ! Stop NML right now
%if $TOPS20 %then STOP_PROGRAM; %fi ! Stop NML right now
NMU$SCHED_ABORT (.TASK);
NMU$SCHED_DESCHEDULE ();
end; ! End of NMU$SCHED_COMPLETE
%global_routine ('NMU$SQUEUE_RESET', QUEUE : ref SQ_HEADER) : novalue =
!++
! Functional description:
!
! This routine resets a "scheduling" type queue. Any queue
! that can have scheduling done on it contains extra information
! in the queue header that should be reset any time the queue
! is reset.
!
! Formal parameters:
!
! .QUEUE Address of queue header
!
! Routine value: none
! Side effects:
!
! Any entries on the queue are lost
! Any tasks that are waiting on this queue have
! their entries on the task wait list deleted.
!
!--
begin
!
! Reset data queue.
!
NMU$QUEUE_RESET (QUEUE [Q_QUEUE]);
!
! Reset queue non-empty event.
! Don't allow SQUEUE operations at interrupt level.
!
NMU$SCHED_EVENT (QUEUE [Q_EVENT], $false);
end; ! End of NMU$SQUEUE_RESET
%global_routine ('NMU$SQUEUE_INSERT', QUEUE : ref SQ_HEADER, ENTRY) : novalue =
!++
! Functional description:
!
! This routine inserts an entry into a queue. If a task is
! waiting for this queue to become non-empty, it is scheduled
! for execution.
!
! Formal parameters:
!
! .QUEUE Address of a queue header block
! .ENTRY Address of a entry to be inserted to queue
!
! Routine value: none
! Side effects:
!
! If the queue becomes non-empty the queue of tasks waiting
! for this to happen is checked. If a task is waiting, it
! is put onto the queue of runnable tasks.
!
!--
begin
NMU$QUEUE_INSERT (QUEUE [Q_QUEUE], .ENTRY);
NMU$SCHED_FLAG (QUEUE [Q_EVENT]);
end; ! End of NMU$SQUEUE_INSERT
%global_routine ('NMU$SQUEUE_REMOVE', QUEUE : ref SQ_HEADER) =
!++
! Functional description:
!
! This routine removes an entry from a queue. If the queue
! is empty, the calling task is descheduled and a new task
! is selected for running. If no task is runnable, the null
! task is executed.
!
! Formal parameters:
!
! .QUEUE Address of a queue header block
!
! Routine value:
!
! Address of entry from queue
!
! Side effects: none
!
!--
begin
local
ENTRY;
while (ENTRY = NMU$QUEUE_REMOVE (QUEUE [Q_QUEUE])) eql 0 do
NMU$SCHED_WAIT (QUEUE [Q_EVENT],0);
.ENTRY
end; ! End of NMU$SQUEUE_REMOVE
%global_routine ('NMU$QQUEUE_RESET', QUEUE : ref QQ_HEADER, QUOTA) : novalue =
!++
! Functional description:
!
! This routine resets a "quota scheduling" type queue. Any queue
! that can have scheduling done on it contains extra information
! in the queue header that should be reset any time the queue
! is reset.
!
! Formal parameters:
!
! .QUEUE Address of queue header
! .QUOTA Number of Entries in queue before insert waits
!
! Routine value: none
! Side effects:
!
! Any entries on the queue are lost
! Any tasks that are waiting on this queue have
! their entries on the task wait list deleted.
!
!--
begin
!
! Reset the scheduler queue portion of this entry.
!
NMU$SQUEUE_RESET (QUEUE [QQ_SQUEUE]);
!
! Reset insert queue resume event.
! Don't allow SQUEUE operations at interrupt level.
!
NMU$SCHED_EVENT (QUEUE [QQ_IEVENT], $false);
QUEUE [QQ_QUOTA] = .QUOTA;
QUEUE [QQ_CURRENT] = 0;
end; ! End of NMU$QQUEUE_RESET
%global_routine ('NMU$QQUEUE_INSERT', QUEUE : ref QQ_HEADER, ENTRY) : novalue =
!++
! Functional description:
!
! This routine inserts an entry into a queue. If a task is
! waiting for this queue to become non-empty, it is scheduled
! for execution. If this insert would exceed the insert quota
! the current task is suspended.
!
! Formal parameters:
!
! .QUEUE Address of a queue header block
! .ENTRY Address of a entry to be inserted to queue
!
! Routine value: none
! Side effects:
!
! If the queue becomes non-empty the queue of tasks waiting
! for this to happen is checked. If a task is waiting, it
! is put onto the queue of runnable tasks.
!
!--
begin
while .QUEUE [QQ_CURRENT] geq .QUEUE [QQ_QUOTA]
do NMU$SCHED_WAIT (QUEUE [QQ_IEVENT],0);
QUEUE [QQ_CURRENT] = .QUEUE [QQ_CURRENT] + 1;
NMU$SQUEUE_INSERT (QUEUE [QQ_SQUEUE], .ENTRY);
end; ! End of NMU$QQUEUE_INSERT
%global_routine ('NMU$QQUEUE_REMOVE', QUEUE : ref QQ_HEADER) =
!++
! Functional description:
!
! This routine removes an entry from a queue. If the queue
! is empty, the calling task is descheduled and a new task
! is selected for running. If no task is runnable, the null
! task is executed. When a remove has dropped the number of
! queue entries to less than the quota the queue inserter is
! resumed.
!
! Formal parameters:
!
! .QUEUE Address of a queue header block
!
! Routine value:
!
! Address of entry from queue
!
! Side effects: none
!
!--
begin
local
ENTRY;
ENTRY = NMU$SQUEUE_REMOVE (QUEUE [QQ_SQUEUE]);
QUEUE [QQ_CURRENT] = .QUEUE [QQ_CURRENT] - 1;
if .QUEUE [QQ_CURRENT] lss .QUEUE [QQ_QUOTA]
then NMU$SCHED_FLAG ( QUEUE [QQ_IEVENT]);
.ENTRY
end; ! End of NMU$QQUEUE_REMOVE
%global_routine ('NMU$QQUEUE_EXTRACT', QUEUE : ref QQ_HEADER, ENTRY) =
!++
! Functional description:
!
! This routine removes an explicit entry from a queue. The
! quota for the queue is adjusted to reflect the entry's
! extraction.
!
! Formal parameters:
!
! .QUEUE Address of a queue header block
! .ENTRY Address of entry on queue
!
! Routine value:
!
! $true Entry was found on the queue and extracted
! $false Entry was not found on the queue
!
! Side effects: none
!
!--
begin
if NMU$QUEUE_EXTRACT (QUEUE [QQ_SQUEUE], .ENTRY)
then
begin
QUEUE [QQ_CURRENT] = .QUEUE [QQ_CURRENT] - 1;
$true
end
else
$false
end; ! End of NMU$QQUEUE_EXTRACT
%global_routine ('NMU$SCHED_EVENT', EVENT : ref EVENT_BLOCK, INT_OFF) : novalue =
!++
! Functional description:
!
! This routine resets the data base associated with a
! particular event.
!
! Formal parameters:
!
! .EVENT Address of event block (EVENT_BLOCK)
! .INT_OFF Boolean flag indicating if interrupts
! are to be turned off when this event
! is being signalled. (i.e. this flag is
! $true if the event can be signalled from
! interrupt level.)
!
! Routine value: none
! Side effects:
!
! The queue of tasks waiting for the event is cleared.
! The count of event occurances is cleared.
!
!--
begin
%debug (EVENT_TRACE, (TRACE_INFO ('Event reset (%O)', .EVENT)));
!
! Reset queue of tasks waiting for this event.
!
NMU$TABLE_CLEAR (EVENT [EB_TASKS]);
! EVENT [EB_TASKS] = 0;
!
! Lock the event semaphore, indicating is hasn't
! happened yet.
!
!
! LOCK (EVENT [EB_SEMAPHORE]);
EVENT [EB_SEMAPHORE] = $false;
!
! Set the interrupt suppression flag (if event
! can be signalled from interrupt level.
!
EVENT [EB_NOINT] = .INT_OFF;
end; ! End of NMU$SCHED_EVENT
%global_routine ('NMU$SCHED_FLAG', EVENT : ref EVENT_BLOCK) : novalue =
!++
! Functional description:
!
! This routine flags an "event" occurance. Any task that
! is waiting for the event will be put onto the RUNNABLE
! task queue.
!
! Formal parameters:
!
! .EVENT Address of selected event queue header
!
! Routine value: none
! Side effects:
!
! Tasks may be put onto the runnable queue.
! The queue associated with the EVENT will be emptied.
!
!--
begin
local
TASK : ref TASK_BLOCK;
%debug (EVENT_TRACE, (TRACE_INFO ('Event flagged (%O)', .EVENT)));
!
! If can be flagged from interrupt level .. turn
! interrupts off for a moment.
!
if .EVENT [EB_NOINT] then INTERRUPT_OFF;
!
! Signal that the event has happened by either
! scheduling the next task queued to the event
! or unlocking the semaphore if no task is ready
! for the event.
!
TASK = 0;
incr INDEX from 1 to NMU$TABLE_MAX (EVENT [EB_TASKS]) do
if NMU$TABLE_FETCH (EVENT [EB_TASKS], .INDEX, TASK) then
begin
MAKE_TASK_RUNNABLE (TASK [TB_SCHED_QUEUE]);
NMU$TABLE_DELETE (EVENT [EB_TASKS], .INDEX);
end;
!
! Now we clear the table pointed to by the event block, so that people who
! just deallocate the block without calling NMU$SCHED_EVENT, won't make us lose
! the memory taken up by the table. (At this point all of the useful info in
! the table has been extracted.)
!
NMU$TABLE_CLEAR (EVENT [EB_TASKS]);
EVENT [EB_SEMAPHORE] = $true;
!
! Turn interrupts back on if they were
! turned off.
!
if .EVENT [EB_NOINT] then INTERRUPT_ON;
!
! If we can be called at interrupt level, issue a PROCESS_WAKE in case this
! interrupt occurred between the time _DESCHEDULE checked the RUN_QUEUE, and
! the time it does a PROCESS_SLEEP. This will nullify the PROCESS_SLEEP.
!
if .EVENT [EB_NOINT] then PROCESS_WAKE;
end; ! End of NMU$SCHED_FLAG
%global_routine ('NMU$SCHED_WAIT', EVENT: ref EVENT_BLOCK, SECONDS) : =
!++
! Functional description:
!
! This routine blocks a process until it is woken up on
! any event (or queue non-empty) that it is waiting for,
! or until the timeout interval (if specified) passes.
!
! Formal parameters:
!
! .EVENT Address of event block on which to wait
! SECONDS Number of seconds to wait before timing out
!
! Routine value: $true if the awaited event happened or if no timeout requested
! $false if it timed out
! Side effects: none
!
!--
begin
local
TASK : ref TASK_BLOCK,
TABLE_INDEX,
EVENT_OCCURRED;
%debug (EVENT_TRACE, (TRACE_INFO ('Event wait (%O)', .EVENT)));
!
! Turn the interrupts off if this event
! can be signalled from interrupt level.
!
if .EVENT [EB_NOINT] then INTERRUPT_OFF;
!
! Test for the event having occured already.
! Deschedule the task if not. Turn the
! interrupts on in any case.
!
! EVENT_OCCURRED = LOCK (EVENT [EB_SEMAPHORE]);
EVENT_OCCURRED = .EVENT [EB_SEMAPHORE];
EVENT [EB_SEMAPHORE] = $false; ! Reset the flag after reading it
if not .EVENT_OCCURRED ! If event has not yet occurred,
then ! add current task to task list
begin ! for this event.
TABLE_INDEX = NMU$TABLE_INSERT (EVENT [EB_TASKS], CURRENT_TASK);
if .EVENT [EB_NOINT] then INTERRUPT_ON; ! Enable interrupts
if .SECONDS eql 0 ! If no time specified,
then ! just suspend task.
begin
NMU$SCHED_DESCHEDULE (); ! When task resumes...
return $true ! Return
end
else ! If a wait time was specified,
NMU$SCHED_SLEEP (.SECONDS); ! suspend task by sleeping.
!
! Task will resume here if sleep time expires or event occurs.
! If it timed out, task needs to be removed from event task
! list; if event occurred, task needs to be removed from
! TIME_QUEUE. We try to do both -- no ill side effects.
!
if .EVENT [EB_NOINT] then INTERRUPT_OFF; ! Interrupts off again
! EVENT_OCCURRED = LOCK (EVENT [EB_SEMAPHORE]);
EVENT_OCCURRED = .EVENT [EB_SEMAPHORE];
EVENT [EB_SEMAPHORE] = $false; ! Reset the event flag after reading
NMU$TABLE_DELETE (EVENT [EB_TASKS], .TABLE_INDEX);
TASK = CURRENT_TASK;
NMU$QUEUE_EXTRACT(TIME_QUEUE, TASK [TB_SCHED_QUEUE]);
if .EVENT [EB_NOINT] then INTERRUPT_ON;
return .EVENT_OCCURRED
end;
if .EVENT [EB_NOINT] then INTERRUPT_ON;
$true
end; ! End of NMU$SCHED_WAIT
%global_routine ('NMU$SCHED_DESCHEDULE') : novalue =
!++
! Functional description:
!
! This routine deschedules the currently running task. It checks
! to see if any tasks waiting for events have timed out, and causes
! their events to be flagged if so (see NMU$TIMEOUT_CHECK).
! If no other task wants to run, the null task is run.
!
! Formal parameters: none
!
! Routine value: none
! Side effects: none
!
!--
begin
local
TASK : ref TASK_BLOCK;
TASK = 0;
!
! Wait until a task is found and scheduled
!
while .TASK eql 0
do
begin
!
! Turn interrupts off so that the scheduler is not
! interrupted while working on the run queue.
!
INTERRUPT_OFF;
!
! Get the next entry from the run queue.
!
TASK = GET_NEXT_TASK ();
!
! Turn the interrupts back on now.
!
INTERRUPT_ON;
!
! Switch context to either the new task (if
! one was removed from the queue or the the
! null task.
!
if .TASK neq 0
then
if .TASK neq CURRENT_TASK
then
begin
%debug (SCHEDULER_TRACE,
(TRACE_INFO ('Switch to (%O) %A',
.TASK, ch$ptr (TASK [TB_NAME]))));
CONTEXT_SWITCH (.TASK);
end
else
begin
%debug (SCHEDULER_TRACE,
(TRACE_INFO ('Continuing current task')));
1;
end
else
begin
%debug (SCHEDULER_TRACE,
(TRACE_INFO ('No runnable task .. sleeping')));
%if not $TOPS10 %then PROCESS_SLEEP (60);
%else PROCESS_SLEEP (0); %fi
%debug (SCHEDULER_TRACE,
(TRACE_INFO ('Program woken up')));
end;
end;
!
! When context switched back to this task...
! this routine will return.
!
end; ! End of NMU$SCHED_DESCHEDULE
%global_routine ('NMU$SCHED_PAUSE') : novalue =
!++
!
! Functional description:
!
! This routine will put the currently running task at the end of the
! RUN_QUEUE, and then deschedule. It's purpose is to allow tasks that
! take a long time to be interrupted. This call should be placed at
! strategic, frequently executed points in the task, such as after
! executing a monitor call that blocks for a while.
!
! In essence, this routine will make the current task be the last task
! to be executed in the RUN_QUEUE. Other tasks are installed by
! interrupt level, or by other tasks, and they will all take precedence
! over the current task, if they were on the RUN_QUEUE already.
!
!--
begin
if .NEXT_RUNNABLE_TASK eql .LAST_RUNNABLE_TASK then return;
MAKE_TASK_RUNNABLE (CURRENT_TASK);
NMU$SCHED_DESCHEDULE ();
end; ! End of NMU$SCHED_PAUSE
%global_routine ('NMU$SCHED_SLEEP', TIME) : novalue =
!++
! Functional description:
!
! This routine is called by any task that wishes to go
! to sleep for a short while. The task will block until
! the specified number of seconds elapses.
!
! Formal parameters:
!
! .TIME Number of seconds to wait.
!
! Routine value: none
! Side effects: none
!
!--
begin
local
TASK : ref TASK_BLOCK;
%debug (SCHEDULER_TRACE, (TRACE_INFO ('Task sleeping for %D seconds', .TIME)));
!
! Get address of currently running task's task block.
!
TASK = CURRENT_TASK;
!
! Set the time to wake this task up, clear the wakeup
! event and queue it to the time wakeup queue.
!
TIME_CURRENT (.TIME, TASK [TB_TIME]);
!First extract the item from the queu if it was in there already. This will
!insure that an item never get in there twice.
NMU$QUEUE_EXTRACT(TIME_QUEUE, TASK [TB_SCHED_QUEUE]);
NMU$QUEUE_INSERT (TIME_QUEUE, TASK [TB_SCHED_QUEUE]);
!
! Signal to the TIMER TASK that someone has modified it's
! data base. Then wait for the TIMER TASK to put this task
! back onto the run queue.
!
NMU$SCHED_FLAG (SKD_EVENT);
NMU$SCHED_DESCHEDULE ();
%debug (SCHEDULER_TRACE,
(TRACE_INFO ('Task woken up')));
end; ! End of NMU$SCHED_SLEEP
%routine ('GET_NEXT_TASK') =
!++
!
! Functional description:
!
! This routine will remove the first runnable task from the runnable
! task queue. First, it will check to see if NEXT_RUNNABLE_TASK and
! LAST_RUNNABLE_TASK point to the same vector entry. If so, then the
! queue is empty. Otherwise, we increment NEXT_RUNNABLE_TASK, and
! get that item out of the runnable task vector.
!
! Routine value:
!
! Zero if there are no runnable tasks.
! Otherwise, the address of the task block.
!
!--
begin
local
VALUE;
if .NEXT_RUNNABLE_TASK eql .LAST_RUNNABLE_TASK then return 0;
INTERRUPT_OFF;
NEXT_RUNNABLE_TASK = (.NEXT_RUNNABLE_TASK + 1) mod RUN_QUEUE_SIZE;
VALUE = .RUN_QUEUE [.NEXT_RUNNABLE_TASK];
INTERRUPT_ON;
.VALUE
end; ! End of GET_NEXT_TASK
%routine ('MAKE_TASK_RUNNABLE', TASK) : novalue =
!++
!
! Functional description:
!
! This routine will install the specified task on the RUN_QUEUE.
! The task will be put at the end of the RUN_QUEUE, so that it will
! be run after all currently waiting tasks. This causes round robin
! type scheduling to occur.
!
! It works by incrementing LAST_RUNNABLE_TASK so that it points at
! a new entry in RUN_QUEUE. If (after incrementing) the LAST_RUNNABLE_
! TASK pointer is equal to the NEXT_RUNNABLE_TASK pointer, we stopcode.
!
! Formal parameters:
!
! TASK Address of task to be installed on RUN_QUEUE.
!
!--
begin
INTERRUPT_OFF;
LAST_RUNNABLE_TASK = (.LAST_RUNNABLE_TASK + 1) mod RUN_QUEUE_SIZE;
if .LAST_RUNNABLE_TASK eql .NEXT_RUNNABLE_TASK then
TASK_ERROR ('Run queue overflowed in MAKE_TASK_RUNNABLE');
RUN_QUEUE [.LAST_RUNNABLE_TASK] = .TASK;
INTERRUPT_ON;
end; ! End of MAKE_TASK_RUNNABLE
%routine ('SCHEDULER_TASK') : novalue =
!++
! Functional description:
!
! This task handles timeouts for other tasks. Other
! tasks queue themselves to this task to be woken up
! after the time specified in the TIME_OUT block in
! their respective task blocks.
!
! Formal parameters: none
!
! Routine value: none
! Side effects: none
!
!--
begin
local
TT_SBLK : TIMER_SCAN_BLOCK;
!
! Flag that no time out interrupt is currently defined.
!
TIME_SET_NULL (TIME_OUT);
!
! Loop doing this task's work forever.
!
while $true do
begin
!
! Get current time.
!
TIME_CURRENT (0, TT_SBLK [TT_NOW]);
TIME_SET_NULL (TT_SBLK [TT_TIME]);
!
! Scan timer queue, scheduling any task that needs
! to be scheduled. Also find the next time to have
! a time out interrupt generated.
!
NMU$QUEUE_SCAN (TIME_QUEUE, TT_SBLK, TT_SCAN);
!
! The time returned in TT_TIME is the time of the closest
! task wakeup. If it is greater than zero, a task is
! waiting for wakeup. If no task is waiting, then clear
! any pending interrupts.
!
! Set a new time out interrupt if needed.
!
if TIME_NULL (TT_SBLK [TT_TIME])
then
begin
TIME_INTERRUPT_CLEAR;
TIME_SET_NULL (TIME_OUT);
end
else
if TIME_TEST (TT_SBLK [TT_TIME], lss, TIME_OUT)
or TIME_NULL (TIME_OUT)
then
begin
TIME_COPY (TIME_OUT, TT_SBLK [TT_TIME]);
TIME_INTERRUPT_CLEAR;
TIME_INTERRUPT_SET (TIME_OUT, TIMEOUT_SIGNAL);
end;
!
! Wait until the next scheduler event occurs. The scheduler
! event includes both time interrupts and another task
! queueing itself to be woken up in the future.
!
NMU$SCHED_WAIT (SKD_EVENT,0);
end;
end; ! End of SCHEDULER_TASK
%routine ('TIMEOUT_SIGNAL') TIMER_INTERRUPT_ROUTINE novalue =
!++
! Functional description:
!
! This routine is called at interrupt level when
! a timer interrupt occurs. It signals the SKD_EVENT
! so that the scheduler task will start again.
!
! Formal parameters: none
!
! Routine value: none
! Side effects: none
!
!--
begin
TIME_SET_NULL (TIME_OUT);
NMU$SCHED_FLAG (SKD_EVENT);
PROCESS_WAKE;
end; ! End of TIMEOUT_SIGNAL
%routine ('TT_SCAN', TASK : ref TASK_BLOCK, DATA : ref TIMER_SCAN_BLOCK) =
!++
! Functional description:
!
! This routine performs the timer queue scaning for the
! scheduler task.
!
! When a task is found that has a wakeup time less than
! the current time, a wakeup event is generated for it.
!
! The closest wakeup time to the current time is kept
! so that a wake up interrupt can be set.
!
! Formal parameters:
!
! .TASK Address of task from time queue
! .DATA Address of timer queue scanning data block
!
! Routine value: none
! Side effects: none
!
!--
begin
!
! Check to see if the task needs to be woken up.
!
if TIME_TEST (TASK [TB_TIME], leq, DATA [TT_NOW])
then
begin
NMU$QUEUE_SCAN_EXTRACT (TASK [TB_SCHED_QUEUE]);
MAKE_TASK_RUNNABLE (TASK [TB_SCHED_QUEUE]);
end
else
!
! Check if wakeup time of task is the closest future
! time.
!
if TIME_TEST (DATA [TT_TIME], gtr, TASK [TB_TIME])
or TIME_NULL (DATA [TT_TIME])
then TIME_COPY (DATA [TT_TIME], TASK [TB_TIME]);
!
! Return value indicating that queue scan should continue to
! the end of the queue.
!
0
end; ! End of TT_SCAN
%global_routine ('NMU$SCHED_CURRENT') =
!++
! Functional description:
!
! This routine returns the current task block address.
!
! Formal parameters: none
!
! Routine value:
!
! A task block address
!
! Side effects: none
!
!--
begin
CURRENT_TASK
end; ! End of NMU$SCHED_CURRENT
end ! End of module SCHED
eludom
! Local Modes:
! Mode:BLISS
! Auto Save Mode:0
! Comment Column:40
! Comment Rounding:+1
! End: