Nos version 1.1
Nos 1.0 is an interim release. The task-switcher is functioning
correctly. A process can be scheduled, executed and interrupted
properly.
Nos 1.1 is a bug fixed version of 1.0. It demonstrates three
processes running concurrently.
How to compile "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:
in nut
(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 . . .
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:
5 disable interrupt
6 enable interrupt
8 save C-state // never run in user-space
9 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