Nut Operating System  (Nos)

How NOS work?

Nos works under NOSS (Nos simulator).  Nos are user-space functions (running in user-space).  Noss is responsible to time-multiplex the nos function (switchp, the task-switcher) execution and user functions execution.  Both are running is user-space.  The main function of NOSS is to issue a control to processor simulator (S-machine) to run a number of instructions before returning to NOSS.  Noss does this by commanding the s-machine simulator (the eval() in C) to run the s-code for a number of instructions and save its C-state.  The first thing Nos does is to run  task-switching (switchp) followed by running the task (at restoreCstate).  This behaviour regards Noss as "interruptor" to s-machine simulator, i.e., s-machine is running some computing process and Noss "interrupts" it at a fixed interval to run "task-switching".

Nos starts by executing "main" function which initialise global variables and creates processes to run user functions by (run (user-fun ...)).   After this start-up, noss enters the main loop:

while there is a task
  run switchp and at restore C-state, run user  ;; by call eval once
  save C-state

This is the code in nos for switchp:

;; status indicates how the process has been interrupted:
;;     time-out, block, end

switchp
  if status is time-out or block
    if one-process
      runnable that process
    else
      set current process to ready
      runnable next process
  if status is end
    delete current process
    runnable next process
 
runnable
  set process to running
  restore C-state

Most functions in nos (switchp, wait, signal) must be atomic, that is, they must run to completion before allowing interruption to switch process.  This is achieved by adding two system calls to s-machine: disable interrupt, enable interrupt.  In the s-machine simulator, the eval() executes a fixed number of instruction by checking the instruction count.  This is the main fetch-execute cycle of s-machine (in fact most processor simulator are like this):

eval
  count = 0
  loop
    if count > limit break
    fetch an instruction
    execute the instruction
    count = count + 1

To implement "interrupt", a flag (intflag) is used to disable the break.  This flag can be turned on/off by the system calls.

eval2
  count = 0
  loop
    if intflag == 1
      if count > limit break
    fetch an instruction
    execute the instruction
    count = count + 1

The process descriptor contains C-state. C-state consists of fp, sp and ip.  Saving and restoring C-state are the act of transferring C-state between the s-machine simulator state and C-state of the process. Noss does the restoring of C-state by running the code in user-space. This restoring will affect the flow of execution, as the instruction pointer is changed, it does a jump in the program.  Therefore, the precise point of time when this jump occurs is important.  There must not be any code following this jump.  This instruction must be the last instruction execute in the function (and it never returns to the caller).  As Noss is responsible to run the task-switcher, it must save C-state.  Saving C-state can not be done in user-space as the precise state has been changed when trying to run the "saving state" function.  So, save-Cstate is done in the main loop of Noss (in C).  This gives the save-Cstate a special previlege (so called "kernel" in OS vocabulary).  It is implemented as a system call instruction in s-machine.

The system calls that support Nos are:

20 disable interrupt
21 enable interrupt
22 block current process
23 save C-state  // never run in user-space
24 restore C-state

Example session

A user program is written as (count) and integrated with nos in "main":

; ---- application --------

(def count (n) (i)
  (do
  (set i 0)
  (while (< i n)
    (do
    (set i (+ i 1))
    (print i) (space)))))

(def main () (p)
  (do
  (sys 5)
  (set activep 0)
  (set sseg 1000)
  (run (count 500))
  (bootnos)))

The (run (count 500))  creates a process to run (count 500).  (bootnos) starts the process running.

To run NOSS, first compile user functions with NOS in nos.txt:

e:\test>nut21 < nos.txt
print
(fun.1.1 (sys.1 get.1 ))
printc
(fun.1.1 (sys.2 get.1 ))
nl
(fun.0.0 (sys.2 lit.10 ))
space
(fun.0.0 (sys.2 lit.32 ))
not
(fun.1.1 (if get.1 lit.0 lit.1 ))
. . .

Then run NOSS with the a.obj executable:

e:\test>noss < a.obj
print
printc
. . .
setslist
showp
wakeup
signal
wait
run
runnable
switchp
bootnos
count
main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
*
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
. . .
146 147 148 149 150 151 152 153 154 155 156 157 158 159
*
 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
. . .
 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
 260 261 262 263 264 265 266 267 268
*
 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
. . .
*
487 488 489 490 491 492 493 494 495 496 497 498 499 500
*
9345 clocks

e:\test>


The "*" indicates the task-switching (every 1000 instructions).

24 Jan 2005
Prabhas Chongstitvatana