The  Alpha  Micro  system  monitor (SYSTEM.MON[1,4]) is logically
    divided into seven modules.  These modules (in the order in which they
    fall in memory) are:

        SYSMON - base monitor

        TRMSER - terminal service routines

        FILSER - file service routines

        FILERR - file error message routines

        EXEC   - executive program module

        DSKSER - general disk driver routines

        INITIA - the system initialization routine

         SYSMON and  EXEC  are  my  own  names  for  those  modules.   The
    following  discussion  of  SYSMON  reflects  the monitor as of the 4.2
    release.

         The thirty-one sixteen bit word addresses from 0 to 76 octal  are
    the  reserved  core  locations  as  defined  in  the WD16 Programmer's
    Reference Manual that comes with every AM-100  (TM)  board.   A  brief
    summary of these word locations and their functions follow (the reader
    is  advised  to  refer  to  the  manual  for a full description of the
    definitions):

     HEX   OCTAL    FUNCTION
     ---   -----    --------
    00-10  00-20    R0-R5, SP, PC, and PS are saved
                    or fetched for halt or power up
                    (not implemented on the AM-100)

    The following locations contain the value that
    is placed in PC if the described event occurs:

     12     22      buss error
     14     24      nonvectored interrupt powerfail
     16     26      power up/halt option power restore
     18     30      parity error
     1A     32      reserved op code
     1C     34      illegal op code format
     1E     36      XCT (execute single instruction) error
     20     40      XCT trap
     22     42      SVCA table (see note 1 below)
     24     44      SVCB
     26     46      SVCC
     28     50      vectored interrupt table (see note 2 below)
     2A     52      nonvectored interrupt
     2B     54      BPT (breakpoint) trap
     2E     56      I/O priority mask
    30-38  60-70    floating point operation storage
    3A-3C  72-74    not used
     3E     76      floating point error PC

    Note 1:  content of octal 42 plus twice the value of the argument is
    placed in PC.  The content of the word thus addressed by PC is added
    to PC to form the final destination address.

    Note 2:  content of octal 50 plus the device code is placed in PC.
    The content of the word addressed by PC is added to PC to get the
    final destination address.

         Once the monitor is loaded, either by the disk controller board's
    PROM routine, MONTST.PRG, or one a bootstrap loader  (HWKLOD,  WNGLOD,
    etc.), a "CLR PC" is executed which sets the program counter to memory
    location  0.    Initially  that location contains the instruction "JMP
    @#31572", which in the 4.2 release of the monitor, is the  address  of
    the initialization routine, INITIA, which will be discussed later.

         In  that there are no SVCC's currently implemented on the system,
    location 46 octal (the SVCC PC) contains zero.  Therefore, if an  SVCC
    ever  gets  called,  the  program  counter  will be set to location 0.
    After the first clock interrupt, location 0 no longer contains the JMP
    instruction.  Part of the job scheduler's task is to update the  arrow
    display  for  the  DYSTAT program.  This is done by moving octal 15 to
    the memory location contained in the JOBDYS word of  each  job's  JCB.
    If  DYSTAT  is  not running on the system, the JOBDYS address is 0 and
    the original JMP instruction gets modified into a LEA R4,@R5.  When an
    SVCC is executed the AM-100 starts executing garbage  and  the  system
    crashes.

         The  next  section  of  the monitor, starting at octal 100 is the
    system communication area as desrcibed in  appendix  B  of  the  "AMOS
    MONITOR CALLS MANUAL".  In addition to that description, the following
    information is known about selected words in that area:

    SYSTEM - if bit 8 is set the system is running
             from a cartidge disk.

    DEVTBL - points to a contiguous area of memory.
             Each entry consists of four words:

             WORD 0 - device status (odd byte) and
                      drive number (even byte).
                       status byte:
                        bit 0 is always set so that word 0 is
                              never 0
                        bit 1  set = sharable
                        bit 2  set = non-sharable device is
                                     assigned (set by ASSIGN)
                        bit 3  set = same as bit 2, but it is
                                     not known how it is set
                        bit 4  set = mounted
             WORD 1 - device name (packed RAD50)
             WORD 2 - JCB address if device is assigned
                        if address is odd, device is
                        assigned to "COMPUTER B"
             WORD 3 - device bitmap address (if 0, device is not
                      file structured)
             Last word of the table is zero

    CLKQUE, SCNQUE, and RUNQUE are discussed
    later with the job scheduler.

    DRVTRK - the table initially contains -1's

         Following the system communication area is the vectored interrupt
    (I0)  table  and  an  illegal interrupt trap routine.  There are eight
    entries in the table, which initially are offsets to the trap routine.

         The internal stack is 100 octal words is size and is  used  as  a
    work stack by the job scheduler.
         The SVCA and SVCB offset tables are the next area in the monitor.
    The  execution  of an SVCA is described in the WD16 manual.  There are
    no undocumented SVCA's, however there appears to be an error in  those
    copies  of SYS.MAC that I have seen.  The BNKSWP call is defined as an
    SVCA 46, but its table entry in the monitor makes it an SVCA 45.

         SVCB's are processed with a routine which  follows  their  offset
    table.    This  routine  decodes the PSI following the SVCB opcode and
    places the address of the first argument in R4 and the address of  the
    second  argument  in R3.  The function code is placed in R0 and the PC
    address is adjusted up to three words past the SVCB.  The offset table
    is entered with a TCALL R5, which contains twice the value of the SVCB
    argument.  If the SVCB uses an extra argument, as SVCB 0 function code
    octal 14 (DSKALC through DSKCTG) does, it is the responsibility of the
    final destination routine to adjust PC past this word.  There  are  no
    undocumented SVCB's.

         The  monitor  error  trap  routines  follow  the  SVCB processing
    module.  The first of these is the ubiquitous "BUSS ERROR - PC  nnnn".
    The error control intercept words of the JCB are first tested for user
    error  recovery.    If  these words are null (they are always reset to
    null by EXIT - SVCA 11), a system error message is generated.  A "BUSS
    ERROR" is reported for buss, breakpoint, and  floating  point  errors.
    There  are no SVCA 0 or SVCA 1 calls defined and they are trapped out,
    however they will be erroneously reported  as  "??SVCA  1  CALLED"  or
    "??SVCA 2 CALLED".

         The  queue  system  routines  and the initial twenty queue blocks
    form the next section of the  monitor.   The  queue  calls  are  fully
    described in chapter five of the "AMOS MONITOR CALLS MANUAL".  No test
    is  made  by any queue call to determine if there are any queue blocks
    available.  If a particular system installation makes heavy use of the
    queue system, it is recommended that QFREE be tested  before  a  queue
    call is executed.

         The  job  scheduler  follows  the  queue  system.   It is entered
    whenever a nonvectored interrupt (I1) is generated.  This interrupt is
    the line clock.  The job scheduler first executes a SAVE  (PS  and  PC
    were pushed on the stack when the interrupt occured), switches over to
    the  internal  stack,  and  then  increments TIME.  CLKQUE entries are
    processed every clock tick by first picking up the address  of  CLKQUE
    which  links  to  the  first queue entry to be processed and the first
    word of the queue entry links to next, etc.  The second  word  of  the
    queue entry contains the address of the routine to be executed and the
    remaining  six  words  can  be  used for the routine's data (after the
    routine is called R3 will contain the address of the third word of the
    queue entry).  The routine must exit with  a  RTN.   If  the  "V"  bit
    (overflow bit in the status information) is set (via an LCC 2 or other
    operation  which  sets this bit) on return from the routine, the entry
    is deleted from the CLKQUE by returning the queue block to  the  QFREE
    list  and  relinking  the  rest  of  the  queue.     The next entry is
    processed by picking up its link from the previous entry.  Examples of
    CLKQUE entries are the  SLEEP  call  (described  below),  HLDTIM,  and
    DYSTAT  (which is never descheduled).  CLKQUE entries are added with a
    QINS call and placing the address of the routine in the second word of
    the queue block.

         When the end of the CLKQUE is reached, the initial entry will  be
    processed.    This  entry  is two words long; the first containing the
    link to SCNQUE  and  the  second  containing  the  address  of  a  RTN
    instruction (allowing simumlation of an actual routine).

         SCNQUE  entries  are  processed  by the same routine that handles
    CLKQUE entries and the format is identical.  The job  secheduler  does
    not make a distinction between them at this point. When the end of the
    SCNQUE is reached its link points to the RUNQUE.

         The  RUNQUE  is  five words in length; its initial entries are as
    follows:

    RUNQUE: WORD    RUNQUE+6        ; link to current job's run address
            WORD    1004            ; address of a RTN instruction
            WORD    RUNQUE          ; link to next job's run address
            WORD    0               ; last link
            WORD    2476            ; end of the job scheduler

         The RTN instruction (RUNQUE+2) will always by executed  when  the
    RUNQUE is entered (this provides for an initial simulation of a CLKQUE
    or  SCNQUE entry).  If any jobs are scheduled (see JRUN for scheduling
    information) the first RUNQUE word will contain  the  address  of  the
    fourth  word of the currently scheduled job's JOBRNQ.  This address is
    the run address for the job when it gets scheduled and will be  either
    the  entry  point  of  the  job  context  switching routine or the job
    priority timer routine.  If the address is the first one, the job's SP
    is restored from JOBRNQ+14, the job's  priority  is  copied  into  the
    timer word and incremented (to insure the job doesn't get 65,535 ticks
    of  CPU  time),  JOBCUR  is updated, the address of the priority timer
    routine is placed in the fourth word of JOBRNQ, the  DYSTAT  arrow  is
    sent,  and  memory bank switching (if active) is carried out.  An RRTT
    then sends the job off to continue whatever it was doing before  being
    interrupted.

         When  the  priority  timer  routine  is  entered  (either  via an
    interrupt or JWAIT - see below), the time counter word (JOBRNQ+10)  is
    decremented  and, if non-zero, the job is allowed to continue.  If the
    job has used all its alloted time and there  are  other  jobs  in  the
    RUNQUE,  a  new  job is scheduled (if no other jobs are scheduled, the
    job is allowed to continue).   Before  scheduling  the  next  job  the
    current  job's  SP  is saved, the context switching routine address is
    placed in the job's run address word, and the DYSTAT arrow is cleared.
    The link to the next job is loaded from RUNQUE+4 and the link  to  the
    next job up is placed in RUNQUE+4.  This is somewhat oversimplified as
    the  RUNQUE  linkage  appears  to be circular, but that is the general
    idea.

         The initial link defines the end of the RUNQUE.  If all scheduled
    jobs are completed (and descheduled) within the clock tick (or no jobs
    were scheduled) the last link is reached.  This link is to  a  section
    of  the  job scheduler that insures interrupts are enabled (so another
    clock interrupt will occur) and executes an SOB 400 times  (presumably
    waiting  for an interrupt).  If a clock interrupt does not occur after
    the SOB argument reaches 0, the processor is  locked  and  the  SCNQUE
    entries are processed again.

         After  the  job  scheduler  is the BNKSWP routine which will swap
    memory banks for the job in control of the  CPU.   The  job  scheduler
    does  not  use  this call, but rather, does it's own swapping.  BNKSWP
    will not update the JOBBNK word in the JCB and if  the  bank  argument
    passed  in R1 does not exist the job will either be stuck here forever
    or return the wrong or non-existant memory bank.

         Although JWAIT occupies the next block of memory,  discussion  of
    this  call  is  postponed until after JRUN so that the scheduling of a
    job can be done first.

         The JRUN call first locks the processor and  then  picks  up  the
    flag  argument following the call (when the routine returns, the PC is
    adjusted past this argument).  If none of the flag argument  bits  are
    set  in  the current JOBSTS word, the routine returns. If any bits are
    set, they are cleared from the JOBSTS word (if the  flag  argument  is
    zero,  it is a special case and the bit test is bypassed).  The JOBRNQ
    words are defined below to aid in  the  following  discussion  on  job
    scheduling.

    JOBRNQ (seven word block):

    WORD 0 - unknown / always 0
    WORD 1 - link to last scheduled job
    WORD 2 - link to next scheduled job
    WORD 3 - job's run address
    WORD 4 - priority counter
    WORD 5 - job priority
    WORD 6 - current SP

         The  link  to the next scheduled job (JOBRNQ+4) is tested and, if
    it is non-zero, the routine returns as the job is  already  scheduled.
    If the link is zero, it is loaded with the link from the job currently
    in control of the CPU to the next scheduled job.  The link to the last
    scheduled  job (JOBRNQ+2) is loaded with the link to the job currently
    in control.  Thus, the JOB referenced by R0 when a JRUN is executed is
    inserted between  the  job  which  executed  the  JRUN  and  the  next
    scheduled  job.    Because the interrupts were disabled by the call to
    JRUN, the job will be the next one scheduled.  Obviously, a JRUN  with
    R0  referencing  its  own  job will accomplish nothing as the job must
    have been scheduled to execute the JRUN.

         In the JWAIT call the flag argument is picked up,  its  bits  are
    set  in  the  JOBSTS  word, and PC is adjusted past the argument.  The
    JWAIT functions in an opposite manner to JRUN.  JWAIT  links  the  job
    scheduled  before  the one referenced by R0 to the job scheduled after
    it and clears the forward pointing link in JOBRNQ+4(R0).  If the  link
    was  already  cleared, JWAIT is exited as the job is already in a wait
    state.  The job's SP is saved in JOBRNQ+14, the internal stack address
    is loaded, the RUNQUE is indexed, the DYSTAT arrow is cleared, and the
    next job is scheduled.  JWAIT may be called with  R0  referencing  its
    own job; the caller should obviously provide a means for the job to be
    rescheduled.

         The  SCAN  call  follows  JRUN and executes entries in the SCNQUE
    until the link to the RUNQUE is reached.

         The SLEEP call inserts a queue block into the CLKQUE  by  loading
    R3 with the address of the CLKQUE and calling QINS.  Part of the SLEEP
    code is a routine which decrements the tick argument of the SLEEP call
    until it is 0; one decrement per clock tick.  The address of this code
    is placed in the second word of the queue block (the first word in the
    block  is  the  link  to the next CLKQUE entry).  The tick argument is
    placed in the third word of the queue block (referenced  by  R3  after
    the call to the routine) and the job's address is placed in the fourth
    word.    The  job itself is placed in a wait state with "JWAIT J.SLP".
    When the tick argument reaches zero, the job's address  is  picked  up
    from  the  queue  block  and  a "JRUN J.SLP" is executed, clearing the
    J.SLP flag from the job's JOBSTS word and rescheduling the job.

         For those programmers interested in making use of the  CLKQUE  or
    the SCNQUE the following remarks and example should prove helpful:

              As  described  above,  CLKQUE  and  SCNQUE  entries are
         identical in format and in the  method  by  which  they  are
         processed.    The major difference being that SCNQUE entries
         are processed not only at line clock  interrupts,  but  also
         when  the  processor  is  idle  and  when  the  SCAN call is
         executed.  The distinction can be important if  the  routine
         is  to  be used for controlling or monitoring a real time or
         external event.  For the purposes of the following  example,
         an  entry is inserted into the CLKQUE, but the method is the
         same for the SCNQUE.

    EXMPLE: LOCK                    ; no interrupts
            MOV     #CLKQUE,R3      ; load address of chain
            QINS                    ; insert queue block at R3
            LEA     R1,CODE         ; address of routine to be run
            MOV     R1,(R3)+        ; set address of routine in queue block
            MOV     DATA,(R3)+      ; remaining six words can be used for data
            UNLOCK                  ; routine is queued
            ...
            ...                     ; balance of user program
            ...
            EXIT

    CODE:   ...                     ; code which is executed when CLKQUE
            ...                     ; entry is called. R3 will index the
            ...                     ; second word of the queue block for
            ...                     ; use as data area.  R4 must not be
            ...                     ; disturbed!  Calls which index a job
            ...                     ; may not neccessarily index the caller's.
            RTN                     ; the routine must return
         Note:  The block of code that is inserted into either SCNQUE
         or CLKQUE may not reside in bank switched memory, it must be
         in memory common to all users as a job  does  not  run  this
         code,  the  monitor  runs  it.  In addition, the queue entry
         must be deleted from SCNQUE or  CLKQUE  before  the  program
         terminates  which  inserted  it  if the code is part of that
         program.  If the code is another memory module  which  won't
         move  after the program terminates, then the queue entry may
         remain.  Otherwisw, garbage will probably  be  executed  the
         next  time  the  entry  is  processed  resulting in a system
         crash.

              To allow the inserted code to remove itself from CLKQUE
         (or SCNQUE)  when  some  condition  is  met,  the  following
         instructions  (or appropriate alternatives) are added to the
         routine:

    CODE:   ...                     ; user routine
            ...
            TST     VALUE           ; condition met yet?
            BNE     RETURN          ;   no
            LCC     2               ;   yes, set "V" bit to delete entry
    RETURN: RTN

              If it is desired that the routine only be  executed  at
         fixed  intervals  a  SLEEP  call cannot be used as the SLEEP
         call only deschedules jobs, not CLKQUE (or SCNQUE)  entries.
         A  routine  such  as  the  one  described  by Lefford Lowden
         (Method One) in issue Number 2.3 of his Newsletter should be
         used.  The CPU loading is not severe as the overhead of  job
         context switching is not incurred.

              If  the  job  which inserted the queue entry executed a
         "JWAIT  FLAGS",   suspending   itself   until   some   event
         transpired,  the  queue  entry  can revive it by executing a
         "JRUN FLAGS" with R0 indexing the job's JCB before the final
         return.

         Good luck!

         The three memory calls (GETMEM, DELMEM and CHGMEM) are  all  part
    of  the  same  module and include extensive error testing.  The GETMEM
    call will return a module cleared to nulls.

         The most involved module in this section of the  monitor  is  the
    FETCH  /  SRCH  module.    It  is  composed  of  over 150 instructions
    (compared to the barely 70 of the job scheduler).  It first determines
    what control flags have been set and then limits it's search  on  that
    basis.    Unless the F.ABS flag was set a search of either user memory
    (F.USR) or system memory and user memory is made on a module by module
    basis.

         If a disk fetch was requested, FETCH clears all flags  and  error
    codes from the user DDB, except flag bits 4 (transfer initiated) and 5
    (read  or  write).  The DDB is then pre-INITed (BIS #40000,DDB) and an
    INIT DDB is called.  This is done to  get  the  address  of  the  disk
    driver  in  DDB+12  without  allocating a buffer and also, FILSER will
    supply the user's default Device if it was not specified in  the  file
    specification.    The  FETCH  call  uses this address to determine the
    record size of the device (always the first word of  a  disk  driver).
    It  sets  this  record  size in DDB+4 and then checks to make sure the
    caller has enough memory to accomodate a disk  record.   If  not,  the
    call  will  be aborted.  If no PPN was supplied in the DDB, the user's
    PPN is picked up before a search of the disk MFD is made to  find  the
    address of the UFD.  If the UFD was found, it's records are read until
    the  specified  file  and its link are located.  If the file is found,
    its size is calculated and if the  user  has  enough  free  memory,  a
    memory  module  is built and the file is read in. If the user included
    the F.FIL control flag, the file's name will be copied from the DDB to
    the housekeeping words of the memory module and the module "FIL"  flag
    will be set.

         The  KBD call follows the FETCH / SRCH module.  If the job has no
    terminal attached a "JWAIT J.TIW" is executed,  descheduling  the  job
    until  a terminal is attached.  If the terminal is in image mode a TIN
    is called and the input character is placed directly in  R1.   If  the
    terminal  is  in  normal  input mode, the job's command file size word
    (JOBCMZ) is tested for command file processing.  If the word  is  zero
    (not  processing  a  command file) TIN is called, adding characters to
    the input buffer, until either a line-feed is reached or the buffer is
    full.

         The KBD routine also handles command  file  processing.   If  the
    JOBCMZ  word is non-zero, KBD will get its input from the command file
    buffer, echoing the data if the Trace flag is set and placing it  into
    the  input  buffer, until a line-feed or a special command file symbol
    (delimited by ":") is reached. If the command is ":<" the data is only
    echoed until a ">" is reached.

         The TTY call tests the JOBCMZ word to determine if a command file
    is being processed.  If the JOBCMZ word is zero, no  command  file  is
    loaded  and  TTY calls TOUT.  If the JOBCMZ word is non-zero, bit 4 of
    JOBCMS is tested to determine if the Trace flag is set;  if  not,  TTY
    returns.   If Trace is set, bit 2 of JOBCMS (Revive) is tested and, if
    it is set, TOUT is called else TTY returns.

         The TTYI call executes a TTYL until a null character is  reached.
    TTYL  executes  a TTY.  In the TTYL call, a carriage return (octal 15)
    gets an automatic line-feed (octal 12) appended.

         The TAB call executes a TTYI with octal 11  and  0  as  immediate
    data  and  a  CRLF does the same, but with octal 15 and 0 as immediate
    data.

         The terminal service routines follow the preceding calls and they
    will be discussed in the next article.