Operating System NOS 

A computation task is performed in an operating system as a process.  A process is a program in execution.  A process consists of a program and its attributes for run-time environment such as computation state and other data structure to manage process at run-time.

Multi-task is implemented by time-slicing. Each process takes turn to use the processor to execute its code until its time-slice is over then the next process is started. To divide time into slices, one can visualise that an interrupt event occurs periodically and the processor jumps to execute a special code called Interrupt Service Routine (ISR). Upon completion of ISR, the processor returns back to its process.

The main operating system code to manage multi-task is the task switcher (switchp). Its job is to select the next process. Here is the time line of a processor running a multi-task operating system:

  p1 || p2 || p3 || ...

  -----> t

where p1, p2 ... are processes and || denotes the task-switching at the interrupt event.  In Nos, there are three types of interrupt events: timeout, block, stop.  Timeout occurs when a process used up its time-slice.  Block is an event that a process is suspended when it requests a operating system service. Stop denotes a process termination.

Task-switcher

Here is the code of the task-switcher

switchp()
  di()
  event = getevent()
  if(event == STOP)
    setValue(activep,DEAD)
    activep = deleteDL(activep)
  else
    setValue(activep,READY)
    activep = getNext(activep)
  ei() 

activep holds the (circular) list of processes scheduled to be run. di() is a disable interrupt function. ei() is an enable interrupt function.  They prevent switchp from being interrupted during the task-switching.

To create time-slicing we run Instruction Level Simulator (ILS) for the target machine (such as s-code, sx-chip, som-v2x) and at the end of executing an instruction we check for "interrupt". To actually start/stop a process by interruption, the supervisor code is used to emulate Interrupt Service Routine (ISR) in Nos.   The supervisor code save/restore the processor state to/from the data structure of a process. It is performed "at a higher level" above ILS. (In an actual processor it is implemented as a privilege code).

The code for the operating system is executed in the "user space".  Only a tiny part of OS must have a special privilege, this part is the supervisor.  The supervisor (called Noss, Nut Operating System Supervisor) manages the data structure of the task (called Process Control Block) which involves save/restore the computation state (such as PC, FP, SP) and initiate the operating system code to be executed. 

Supervisor Noss

The supervisor is implemented as a function to be called when an interrupt event occurs. It has two internal states: task-switch (psw) and user.  When an interrupt occurs, Noss is called (it is in psw state), the current user process (in the queue at activep) is suspended, the task-switcher (it is a special process, switchp) is started. Switchp runs to its completion and calls Noss, activep by now is updated to the next process in the queue. Noss (it is in user state) resumes the process in the queue. Here is the time line of task-switching:

  p1  ^psw switchp ^user p2 ...

  -----> t

where ^psw  denotes an interrupt to call noss (in psw state), ^user denotes call to noss (in user state) when switchp runs to its completion. Here is a pseudo code of noss function.

noss
  switch(noss_state)
    psw: 
       save computation state of userp
       start switchp
       noss_state = user
    user:
       userp = activep
       restore computation state of userp
       noss_state = psw

The computation state consists of FP,SP and PC. Here is how the ILS looks like:

eval
  while runflag do
    clock = clock + 1
    fetch
    decode
    do each instruction
       add: ...
       jmp: ...
       stop: noss(STOP)
       block: noss(BLOCK)
       ...
    if time-out then noss(TIMEOUT)

To simulate a time-out, the number of instruction executed is counted (clock). A time-out occurs when it exceeds a threshold (QUANTA).  

Process creation

A special syntax is used to create a process:

   p = run( function_name() )

"run" is compiled into a sequence that will call function_name.  The function "run" itself is a Nos operating system code to create a process with appropriate attributes (in its data structure) including the correct pointer to body of code of function_name (ads).  Here is the "run" function:

run(ads)
  p = newp()
  setId(p,pid)
  pid = pid + 1
  p[6] = ads
  activep = appendDL(activep, p)
  return p

where newp() creates a new Process Control Block that holds attributes.

Nos services

mutual exclusion (mutex)

  wait(semaphore)
  signal(semaphore)
  initsem(semaphore)

message passing

  send(p,message)
  receive(p)

call to suspend the current process

  blockp()  

boot the operating system by

  bootnos() 

Practice session

Nos and a user program are combined into a single source file.  It is compiled with an extended Rz compiler (rz34). The object code is run by a special instruction level simulator that incorporates Noss function.  Here is an example session:

Assume the source is (myapp.txt):

....

main()
  init()
  psw = run(switchp())
  activep = 0
  // start user process
  showProduce()
  bootnos()

The first three lines are required preliminaries to create the task-switcher process (not in the normal process list).  The user program is "showProduce()".  At the end the operating system is started by "bootnos()". 

Compile the source:

C:> rz34 myapp.txt

The compiler outputs an object file. Run the object file with noss:

C:> noss myapp.obj

The screen output may look like:

load program, last address 718
DP 2024
 * 2  *  * 3 4  *  * 5 6  *  * 7 8  *  * 9
3375 inst. (system 288 user 3087) switchp 9

DP shows the amount of data segment in used. * denotes task-switching.  The last line reports the total number of instructions executed, divided into system and user where system means the number of instructions performing switchp. Lastly the total number of task-switching is reported.

A call stack trace can be turned on (usually for debugging purpose) by changing the definition of TRACE symbol in the header file (compile.h)

#define TRACE

The whole instruction set simulator must be recompiled.  Here is a sample of the screen output after turning on the trace mode (so called Godmode):

C:> noss myapp.obj

load program, last address 718
DP 2024
  call main
    call init
...
    call showProduce
      call run
...
    call bootnos
      call produce
        call send
...
          call blockp

noss clock 530 active 3
save pid 2 fp 1000 sp 1001 ip 8
event 11 run task switcher

            call switchp
              call di
              call getevent
              call setValue
              call getNext
              call ei

noss clock 562 active 3
restore pid 3 fp 3000 sp 3001 ip 715

          call consume
            call receive
              call di
              call getMbox
              call findmail
              call deleteDL
...
noss clock 3375 active 0

3375 inst. (system 288 user 3087) switchp 9

QUANTA can be changed to observe the behavior when the time-slice is changed by changing its definition in the header file (compile.h)

#define QUANTA  400

Again, the whole ILS must be recompiled for the change to take effect.

end

last update  4 Sept 2011