Noss  

nos-supervisor (or nos simulator)  

Noss is a previledge program. A previledge program is a program that is "out-of-bound" of user programs.  A previledge program provides mechanism for executing a user program, for example,  Sx-sim is a previledge program that "execute" s-code.  In our implementation, previledge programs are written in C.  User programs are written in Nut. Nos is a user program.

Noss controls sx-sim.  Noss drives sx-sim to execute instructions for a "quanta".  A quanta is a time period counted as clock cycles.  The eval loop of sx-sim executes one instruction and checks for time-out.  The number of cycle used is accumulated.  When this number exceeds "quanta", noss takes action by calling "switchp" in nos.

"switchp" saves the current computation state.  The computation states are pc, sp, fp and ts (program counter, stack pointer, frame pointer, Sx processor also has a top of stack register) of the current process.  They are saved to the process descriptor (the data structure to hold states of a process).  The next process in the process queue is then activated.  Its computation state is then "loaded" into sx-sim. At this point, the user program (the current process) resumes its execution and continues until noss interrupts it. "switchp" is a previledge process, it is allowed to run to completion without interruption.

The situation above describes a time-out situation where a process has used up its own time allocation.  Other kind of interrupts are, when the process is blocked, and when the process is terminated.  The "blocked" process will be discussed later when we do message passing and other resource sharing.

Noss is minimal in the sense that it does not do a lot of things by itself.  The only thing it does is to call "switchp".  "switchp" is in user space. noss monitors the state of computation of a process through global variables: status and activep.  The main loop of noss is "boot".  It is as follows.

void boot(void){
    . . .

    eval(1);       // boot nos
    while(M[a_activep] != 0){    // interrupt-execute loop
        M[a_status] = status;    // update to nos
        printf(" * ");
        eval(callswitchp);      // run switcher
    }
}
eval(1) executes "main" user function which will start the operating system (nos) and creates process queue.  At the end of "main" function, it calls "bootnos" (user-space) which calls "runnable" to start the current active process.  "runnable" issues a kernel call to "restore C-state" (privilege program) in noss to switch to user-process and executes it until interrupted (time-out/blocked/terminated). M[a_activep]  and M[a_status] are global variables in user-space, they are used to communicate between noss and nos. They appear as "activep" and "status" in nos.

how to compile "run"?

s-obj must contain symbol table with the correct references.  s-obj is generated by gen2.txt.  However, gen2.txt just passes symbol table through.  The symbol table is read from n-obj.  n-obj is generated by nut.txt, the compiler.  The current version dumps everything in the symbol table.

The symbols that must be exported are of type FUN and GVAR only.  The following tasks must be done.

1) change nut.txt to output only the necessary one.
2) change gen2.txt to output the s-code reference.  However the number of symbol does not change.

at nut.txt

dumpsym is responsible to output the symbol table.  relocate the reference to function such that the code segment starts at 2.  It is necessary as nut-compiler is used under "nsim" where both the compiler and the user program to be compiled occupy the same code segment.  Therefore the user program in the code segment will not start at 2.  We would like the object to be relocatable, therefore the user code should start at 2.

When starting the compiler, (sys 9) is used to find out where to user code segment is.  The global variable "Start" stores this location, and it is used to relocate the reference to all function call when output the object.

The following code is added in nut.txt at dumpsym, to output the correct reference.  The data segment is not relocated as it is already started at 0.

      (if (= ty tyFUN)
        (set n (shift (getVal i) Start))
        ; else
        (set n (getVal i)))
      (print n) (space)

at gen2.txt

when read symtab from n-obj, it recognises the type "fun" and outputs the s-address corresponding to the n-address.  Here is the added code to outsym, to output symtab.

    (set ty (atoi tok))
    (tokenise)                    ; ref, reloc
    (if (= ty tyFUN)
      (do
      (set ref (shift (atoi tok) CS))
      (print (assoc ref)))
      ; else
      (prstr tok))
    (space)

how to gen "run"

(run (fn ...))

will generate a call to "run", placing the address pointed to (fn ..) as its argument.

  lit x
  call run
  jmp y
x: ...
  call fn
  end
y: ...

The address of x is the next 3 words.  jmp x skips the code (fn...).  (fn ..) is deferred and "run" will use x as the starting address of the process (fn...). When the process returned, it will be terminated by "end". This is in the function gencall.

; convert arg to index to symtab
; e is arglist
(def gencall (arg e) (idx a)
  (do
  (set idx (searchRef arg))
  (if (= idx Runidx)        ; is "run"
    (do
    (outa icLit (+ XP 3))   ; point to code of process
    (outa icCall idx)       ; call run
    (set a XP)
    (outa icJmp 0)
    (eval (head e))
    (outs icEnd)
    (patch a (- XP a)))     ; jump over
    ; else
    (do                     ; normal call
    (while e
      (do
      (eval (head e))
      (set e (tail e))))
    (outa icCall idx)))))

Example of code generation for "run"

A call to "run" is compiled into passing an address of the code for the function call (the argument of "run" is a function call).  The argument to call.run in n-code is just a list of user-function call with its arguments as usual.  However, this argument
will not be evaluated.  Instead, the address of the code of this call will be generated as an argument of "run".  See the following example:

(def add (a b) () (+ a b))
(def run (f) () 0)
(def main () ()
  (do
  (run (add 4 5))))

in n-code

add
(fun.2.2 (+ get.1 get.2 ))
run
(fun.1.1 (lit.0 ))
main
(fun.0.0 (do (call.17 (call.14 lit.4 lit.5 ))))

generate s-code. See line 12-18

      1 Call main
      2 End
      3 Fun add
      4 Get 2
      5 Get 1
      6 Add
      7 Ret 3
      8 Fun run
      9 Lit 0
     10 Ret 2
     11 Fun main
     12 Lit 15
     13 Call run
     14 Jmp 19
     15 Lit 4
     16 Lit 5
     17 Call add
     18 End
     19 Ret 1

The line "(run (add 4 5 ))" becomes

     12 Lit 15     ;; address of code (add 4 5)
     13 Call run
     14 Jmp 19     ;; do not execute now
     15 Lit 4     ;; the code (call.add lit.4 lit.5)
     16 Lit 5
     17 Call add
     18 End
     19 . . .


last update 17 Aug 2006