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:

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:
  1. link previous
  2. link next
  3. process id
  4. process status
  5. FP
  6. SP
  7. PC
  8. in-box
  9. await-box
  10. message
  11. 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:
  1. its time-slice has been used up, this is called "time-out"
  2. the process has been blocked by executing some operation, this is called "blocked".
  3. 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