Nut Operating System
Try the demo here
Nos composed of user-space functions (running in user-space). Nos
models concurrency using process. The share-resource accesses are
controlled with semaphores. The processes in Nos communicate with each
other through message passing. The crucial real-time functions are supported
therefore Nos can support real-time tasks. Nos supports the following
functions:
- create a process
- terminate a process
- manage the process queue
- task switching
- wait/signal a semaphore
- send/receive a message
- get a real-time clock
- set a timer
Process
A process is an independent computation which can run concurrently with
other process. A process is declared as a normal defined
function. Initial values can be passed as parameters at the starting
time of a process. A process will end its execution by
self-termination when it executes the last instruction at the end of
program. This is different from the execution of a function which ends
its execution by returning to the caller. A program that calls a
process will start that process execution, that program then will continue
to work without waiting. A process never returns to its caller.
A process is a program in execution. Several processes can be active
at the same time. The concurrency is achieved via multi-threading
("light weight process"). A heavy weight process is a process with a
separate address space. It needs a mechanism to do the address mapping
between virtual address and physical address. A light weight process
has single address space. A thread is a trace of execution, a single
thread process is resulted from co-operative process (via
co-routinte). A multi-thread process has several traces at the same
time. This can be accomplished by pre-emptive scheduler with
time-slicing. A light weight process is much cheaper to create than a
heavy weight process.
Each process has its own stack segment. In Nos, there is a single
address space, the stack segment of all processes are in the same address
space. The advantage is that there is no translation between virtual
address and physical address therefore it is fast and simple. The
disadvantage is that there is no protection between processes. Each
process has its process descriptor (PD) to store the necessary
information. A process descriptor in Nos consists of 11 fields:
- link previous
- link next
- process id
- process status
- FP
- SP
- PC
- in-box
- await-box
- message
- timer
The link previous/next are used to form the list of processes, the process
queue. The process identifier is a unique number used to label a
process. The process status holds the state of process (to be
explained later). The fields {PC, SP, FP} hold the computation state
of the process. The mail-box (in-box and await-box) is used to
communicate between processes. The in-box is the list of processes
that sent messages to this process. The await-box is the list of
processes that are waiting for messages from this process. The timer holds
the timer value of this process.
Scheduler
When a process is created it is ready to start the execution. Its PD will be
linked to the "ready list" (the process queue) which is a doubly linked
circular list used by a scheduler. A scheduler has the duty of
selecting a process to run from the ready list. The scheduling policy
of Nos is a Round-Robin policy where an equal time-slice is allocated for
every process and the process is scheduled on first-come first-serve basis.
The scheduler will enable a process in the ready list to run until its
time-slice is over and then switches to the next process in the list.
If a process enters a "wait" state, it is said to be blocked. Usually,
a process is blocked because it performs some operation that requires
waiting for another process, such as waiting for the receiver to retrieve a
message. When a process is blocked, its PD will be removed from the
ready list. The process in "wait" state can be awaken by other
process. Its PD will be inserted into the end of the ready list.
To perform the switch from one process to another process (task switching),
the current state of computation {PC, SP, FP} of the active process is saved
in its PD and the state of computation of the process to be run is
restored. A process is run until it is time-out, or it is blocked or
it is terminated. "switchp" is the task switcher function in Nos. Here
is how "switchp" work.
switchp
if status is time-out or
blocked
run next task
else status is terminated
delete this task
run next task
where status is the state of the process. This "switchp" function is
implemented as an Interrupt Service Routine. That is, it is called
when an interrupt occurs. A timer is set to interrupt Nos
periodically. Before we go into more details of the operating system
functions, we must understand the basic of running a task first.
Simulation of interrupt
To simulate the "interrupt", the processor checks the signal from time to
time (this is called "yield"). The interrupt interval is controlled by
counting the cycle used (in S23 this is done via timer). In Nos, there
are three interrupt events:
- its time-slice has been used up, this is called "time-out"
- the process has been blocked by executing some operation, this is
called "blocked".
- the task is completed, the program stopped, this is called "stopped".
When interrupt occurs, Nos performs "switchp". The user process is run
until it is time-out/stopped/blocked. "switchp" runs to completion. It
must not be interrupted as it is manipulating the process queue. Then
Nos is going back to run a user process.
At the end of task-switch, it always run the next task. This is
accomplished by restoring the computation state of that process. This
means the PC, SP, FP of that process are restored to the processor simulator
and then the simulator continues to execute until the next interrupt occurs.
Processor simulator
Nos starts with setting up the process descriptor and the process queue
(using a special function called "run") then the first process is started
using "boot nos". Bootnos allocates the processor to the first
process.
In the processor simulator, the main loop executes instructions continuously
for a fixed number of cycles. This is the main fetch-execute cycle of
the processor (in fact most processor simulator are like this):
main
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.
main_with_interrupt
count = 0
loop
if intflag == 1
if
count > limit break
fetch an
instruction
execute the
instruction
count = count + 1
The system calls that support Nos are:
20 disable interrupt
21 enable interrupt
22 block a process
24 stop simulation
How a process is created
A function "run" is used to create a process and put it to the process
queue. Although "run" looks like a normal function, it can not be
compiled into a normal function call. The argument to "run" is a
function call which will be turned into a process so it should not be
evaluated. A call to "run" is compiled into the code that passes an address
of the function call. The argument to call.run is just a 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". This code will be activated by the
scheduler as a process. See the following example:
in Rz language
run(ads)
// dummy for compiler use
add(a,b)
return a + b
main()
run(add(4,5))
generate S2-code. The line "run (add(4, 5))" becomes ***
:main
...
mov r1 #L104 ***
push sp r1
***
jal rads run ***
jmp
L105 ***
:L104
mov r1
#4 ; call add(4,5)
push sp r1
mov r1 #5
push sp r1
jal rads add
mov r1 r28
trap r0 #13 ;
stop process
:L105
...
ret rads
Example session
A user program is written as (count) and integrated with nos in "main":
//
---- application --------
count (n)
i = 0
while ( i < n )
i = i + 1
print(i)
main ()
init()
p = run(count(500))
bootnos()
The "run (count (500))" creates a process to run "count( 500)".
bootnos( ) starts the process running.
To run Nos demo, first compile nos1.txt (the OS and applications) then
assemble it.
e:\test>rz36
nos1.txt
> nos1-s.txt
e:\test>as23 nos1-s.txt
e:\test>copy nos1-s.obj obj.js
Then run Nos demo with the .obj executable file by browsing nos-sim.htm.
last update 22 Jan 2013