460 Notes #4

         460 Notes #4 Process Management

1. Read Unix man pages on
        fork, exec, wait, exit,  
        nice, sleep

2. Processes:
   A process is a sequence of executions regarded as a single entity for the
   purpose of using system resources. Resources include CPU time, memory space,
   opened files, I/O devices, etc.  In general, a "resource" is anything that
   may cause a process to wait.

   When Unix starts, it creates a process P0 by brute-force. P0 uses the Unix
   Kernel function to "fork" a child process P1, which in turn forks other 
   processes. From a user's point of view, the most important process is P1.

   Unix's "fork" creates a child process idnetical to the parent (the caller of
   fork). However, the child usually uses exec() to change its execution image 
   to a different file. So does P1, which changes its image to /etc/init.  For 
   this reason, P1 is also known as the "init" process.

   While executing /etc/init, P1 reads some default configuration files, e.g.
   /etc/inittab, to fork a child process on each terminal (or dial-in 
   line). Then P1 waits for ANY of the children processes to terminate. 
   
   When a process terminates, it wakes up the parent. So when a "terminal
   process" ends (by user logout), it wakes up P1, which usually "forks" 
   another child process on that terminal.

   Each terminal process opens 3 FILEs;  stdin, stdout, stderr,  on its own
   terminal. Then, it changes image to execute "/etc/getty" or /etc/login", in 
   which it displays a  login:  to its stdout and waits for user to login from
   its stdin.  When a user login, the terminal process gets the users uid and
   gid (assigned to the user account in the /etc/passwd file), and becomes the
   user's process.

   The user's account also specifies which program the user will execute when
   login, usually sh.  So, the user process changes image to execute sh, which
   prompts the user for input commands.

   For each input command, sh forks a child to execute the command (file). 
   Meanwhile, sh waits for the child to terminate. When the child dies, it 
   wakes up the parent sh, which prompts for another command.

   If the command line contains | or &, sh may fork several children processes,
   all of which run concurrently.

   If you have taken CS360 from me at WSU, you have written a C program that 
   simulates the Unix sh in a lab assignemnt. In case you did not or forgot, 
   please do the following.
   ***********************************************************************
       Use  man  to read the man pages of  fork, exec, exit and wait 
   ***********************************************************************
3. Process Management in UNIX:
     pid = fork()      : creates a child process
           exec()      : change process image
           exit(value) : terminate with a CODE=value
     pid = wait(int *) : wait for a child to terminate, return its pid and
                         exit status
     nice, sleep       : system calls to dec priority, sleep for secs.
==============================================================================
                         EXERCISES:
              (DO THESE BUT NO NEED TO TURN IN)                           

Write C programs to demonstrate the use of Unix syscalls. 

(1). forkSleep.c:
       fork a child process. 
       Use getpid() and getppid() to identify each process.
       Use sleep(SECONDS) to allow other processes to run first.
       Before termination, print own pid and parent pid, and EXPLAIN
       anything unusual you may have observed.

(2). forkWait.c:
       fork a child. wait for the child to terminate. print its exit status.
       Child process: terminate by    exit(VALUE);
       Parent       : int status;     pid = wait(&status);  
                                      print status in HEX to see its value.
(2). forkExec.c
       fork a child process.  Wait for the child to terminate.
       print dead child pid and exit status.

       Child process:
          Use
             execl("b.out", "b.out", "this", "is", "a", "test", 0);
          to change image to execute a   b.out  file, passing as 
             parameters 
                       "b.out", "this" "is" "a" "test"
             In b.out, echo the parameters, which are argv[1],argv[2],.....

          In b.out:  (READ man on exec)
             use execve() to change image again to  c.out. 

          While in c.out, 
             print the parameters and the environmet variable strings.
               
===============================================================================
4. Process Synchronization in Unix Kernel

   The Unix kernel uses 
                         sleep(int event);
   and                  wakeup(int event);
   for process synchronization.

(1). When a process must wait for something (called an event in Unix kernel)
     it calls
               sleep(event);
     in which it records the event value in the proc structure, changes its 
     status to SLEEP, and gives up CPU. A sleeping process will not run until 
     it is awakened by another process or an interrupt handler through 
     wakeup(event).

(2). An event is anything a process may sleep on, as long as another process 
     will issue a wakeup(event) on that event value. In the Unix kernel, events
     are usually (global) variable addresses in the Unix Kernel, whcih are 
     unique, so that processes may "sleep" on distinct event values, and 
     wakeup(event) only wakes up those processes that are sleeping on the
     specified event.

     Examples: 
       wait for child process termination ==> sleep on its own &proc.
       wait for an i/o buffer ===> sleep on that i/o buffer address.

(3). Many processes may sleep for the same event. (which is natural).

(4). When an event occurs, someone (a process or an interrupt handler) will
     call wakeup(event), which wakes up ALL processes sleeping on that event.
     If no process is sleeping on the event, wakeup() has no effect, i.e. 
     same as a NOP (No Operation).

NOTE: There is no record whether an event has occurred or not. In order NOT 
      to miss the wakeup(event) call, a process must go to sleep() BEFORE 
      the awaited event occurs. In a uniprocessor (Single CPU) system, this
      is always achievable. However, in a Multiprocessor (multi-CPU) system,
      this (sleep first and wakeup later) condition cannot be guaranteed since
      processes can run in "parallel" in real-time. So, sleep/wakeup works
      only for Uniprocessor systems. For multiprocessor systems, we need 
      to use other process synchronization mechanisms, e.g. semaphores, which 
      will be discussed later. 

==============================================================================
(4). Unix kernel assigns a fixed priority to a process when it goes to sleep.
     The priority, which will be the process' scheduling priority when it
     wakeup, is determined by the importance of the "resouces".  Thus, a 
     newly awakened process may have a very high prioirty
    
(5). The assigned priority also classifies a sleeping process as
         SOUND sleepers : those who will not be awakened by "signals"
         LIGHT sleepers : those who will be awakened by signals.

     Examples:
         sleep() for disk I/O completion (interrupt) :   SOUND.
         sleep() for KBD inputs from user            :   LIGHT.
==============================================================================

5. Process Management in MTX
   We shall implement the following operations for process management in MTX:
     
(1). myfork: 
     pid = myfork()  creates a child task and returns the child's pid.
     The child task is READY (in readyQueue). When sechduled to run, it
     RESUMES to the body() function.

     LATER, we shall implement a  pid=ufork() operation IDENTICAL TO fork() in
     UNIX.
        
(2). exit:
     A running task calls myexit(value) to die. The actions of myexit() are:
       Mark itself a ZOMBIE;
       Record an exit value in its PROC.exitValue.
       Give away children (dead or alive) to P1. Make sure P1 does not die if 
       other tasks still exist.
       Issue wakeup(&parentPROC) to wake up its parent;
       Call tswitch(); to give up CPU (for the last time).
     
     NOTE that when a task dies, its PROC is marked as ZOMBIE, contains an exit
     value, but the PROC is NOT yet freed. It will be freed by the parent task
     via the wait() operation.

(3). Wait for a child task to die:
     A task calls  pid = wait(int *status);  to wait for A (ANY) child to die, 
     where pid is the dead child's pid and status is the child's exit value.

           int wait(status) int *status;
           {
              if (no child)
                 return -1 for ERROR;
              while(1){
                 if (found a ZOMBIE child){
                    copy child's exit value to *status;
                    free the dead child PROC;
                    return dead child's pid;
                 }
                 sleep(running);    // sleep at its own &PROC
              }
            }

(4). sleep(event)/wakeup(event)

     A task call sleep(event) to sleep for an event:

       sleep(int event)
       {
          running->event = event;      //Record event in PROC
          running->status = SLEEP;     // mark SLEEP
          // optional: enter a SLEEP list, a hash table, etc.
          tswitch();                   // not in readyQueue anymore
       } 


     A task calls wakeup(event) to wake up ALL tasks sleeping on event:

       wakeup(int event)
       {
          for every task SLEEPing on this event do{
              p->status = READY;        // make it READY
              enqueue(p, &readyQueue)   // enter it into readyQueue
          }
       }
=============================================================================