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