Coupon Accepted Successfully!


Question 1

What do the system calls fork(), vfork(), exec(), wait(), waitpid() do? Whats a Zombie process? Whats the difference between fork() and vfork()?

The system call fork() is used to create new processes. It does not take any arguments and returns a process ID. The purpose of fork() is to create a new process, which becomes the child process of the caller (which is called the parent). After a new child process is created, both processes will execute the next instruction following the fork() system call. We can distinguish the parent from the child by testing the returned value of fork():

If fork() returns a negative value, the creation of a child process was unsuccessful. A call to fork() returns a zero to the newly created child process and the same call to fork() returns a positive value (the process ID of the child process) to the parent. The returned process ID is of type pid_t defined in sys/types.h. Normally, the process ID is an integer. Moreover, a process can use function getpid() to retrieve the process ID assigned to this process. Unix will make an exact copy of the parent's address space and give it to the child. Therefore, the parent and child processes will have separate address spaces. Both processes start their execution right after the system call fork(). Since both processes have identical but separate address spaces, those variables initialized before the fork() call have the same values in both address spaces. Since every process has its own address space, any modifications will be independent of the others. In other words, if the parent changes the value of its variable, the modification will only affect the variable in the parent process's address space. Other address spaces created by fork() calls will not be affected even though they have identical variable names.

Trick question!

How many processes are forked by the below program


Answer is 2 raise to n, when n is the number of calls to fork (2 in this case).

Each process has certain information associated with it including:

The UID (numeric user identity) 
The GID (numeric group identity) 
A process ID number used to identify the process 
A parent process ID 
The execution status, e.g. active, runnable, waiting for input etc. 
Environment variables and values. 
The current directory. 
Where the process currently resides in memory 
The relative process priority see nice(1) 
Where it gets standard input from 
Where it sends standard output to 
Any other files currently open 

Certain process resources are unique to the individual process. A few among these are:


  • Stack Space: which is where local variables, function calls, etc. are stored.
  • Environment Space: which is used for storage of specific environment variables.
  • Program Pointer (counter) : PC.
  • File Descriptors
  • Variables

Undex unix, each sub directory under /proc corresponds to a running process (PID #). A ps provide detailed information about processes running

A typical output of ps looks as follows:

         PID               TTY            STAT           TIME         COMMAND 
         8811              3              SW             0:00         (login) 
         3466              3              SW             0:00         (bash) 
         8777              3              SW             0:00         (startx) 
            .              .               .                .         . 
         1262              p7              R             0:00         ps 

The columns refer to the following:

PID      - The process id's (PID). 
TTY      - The terminal the process was started from. 
STAT     - The current status of all the processes. Info about the process status
           can be broken into more than 1 field. The first of these fields can 
           contain the following entries: 

            R        - Runnable.
            S        - Sleeping.
            D        - Un-interuptable sleep.
            T        - Stopped or Traced.
            Z        - Zombie Process.

The second field can contain the following entry: 

            W       - If the process has no residual pages.

And the third field: 

            N       - If the process has a positive nice value. 
            TIME    - The CPU time used by the process so far.
            COMMAND - The actual command.

The init process is the very first process run upon startup. It starts additional processes. When it runs it reads a file called /etc/inittab which specifies init how to set up the system, what processes it should start with respect to specific runlevels. One crucial process which it starts is the getty program. A getty process is usually started for each terminal upon which a user can log into the system. The getty program produces the login: prompt on each terminal and then waits for activity. Once a getty process detects activity (at a user attempts to log in to the system), the getty program passes control over to the login program.

There are two command to set a process's priority nice and renice.

One can start a process using the system() function call


int main() 

  printf("Running ls.....\n"); 
  system("ls -lrt"); 

The exec() system call

The exec() functions replace a current process with another created according to the arguments given.

The syntax of these functions is as follows:


char *env[]; 

int  execl(const char *path, const char *arg0, ..., (char *)0); 
int  execv(const char *path, const char *argv[]); 

int execlp(const char *path, const char *arg0, ..., (char *)0); 
int execvp(const char *path, const char *argv[]); 

int execle(const char *path, const char *arg0, ... , (char *)0, const char *env[]); 
int execve(const char *path, const char *argv[], const char *env[]); 

The program given by the path argument is used as the program to execute in place of what is currently running. In the case of the execl() the new program is passed arguments arg0, arg1, arg2,... up to a null pointer. By convention, the first argument supplied (i.e. arg0) should point to the file name of the file being executed. In the case of the execv() programs the arguments can be given in the form of a pointer to an array of strings, i.e. the argv array. The new program starts with the given arguments appearing in the argv array passed to main. Again, by convention, the first argument listed should point to the file name of the file being executed. The function name suffixed with a p (execlp() and execvp())differ in that they will search the PATH environment variable to find the new program executable file. If the executable is not on the path, and absolute file name, including directories, will need to be passed to the function as a parameter. The global variable environ is available to pass a value for the new program environment. In addition, an additional argument to the exec() functions execle() and execve() is available for passing an array of strings to be used as the new program environment.

Examples to run the ls command using exec are:

const char *argv[] = ("ls", "-lrt", 0); 
const char *env[] = {"PATH=/bin:/usr/bin", "TERM=console", 0}; 

execl("/bin/ls", "ls", "-lrt", 0);
execv("/bin/ls", argv); 

execlp("ls", "ls", "-lrt", 0);   
execle("/bin/ls", "ls", "-lrt", 0, env);  

execvp("ls", argv); 
execve("/bin/ls", argv, env); 

A simple call to fork() would be something like this


int main() 

  pid_t pid; 
     case -1: exit(1); // fork() error.
     case  0: // Child process, can call exec here. 
     default: // Parent.

The call wait() can be used to determine when a child process has completed it's job and finished. We can arrange for the parent process to wait untill the child finishes before continuing by calling wait(). wait() causes a parent process to pause untill one of the child processes dies or is stopped. The call returns the PID of the child process for which status information is available. This will usually be a child process which has terminated. The status information allows the parent process to determine the exit status of the child process, the value returned from main or passed to exit. If it is not a null pointer the status information will be written to the location pointed to by stat_loc. We can interrogate the status information using macros defined in sys/wait.h.

Macro                  Definition 
WIFEXITED(stat_val);    Nonzero if the child is terminated normally 
WEXITSTATUS(stat_val);  If WIFEXITED is nonzero, this returns child exit code. 
WIFSIGNALLED(stat_val); Nonzero if the child is terminated on an uncaught signal. 
WTERMSIG(stat_val);     If WIFSIGNALLED is nonzero, this returns a signal number. 
WIFSTOPPED(stat_val);   Nonzero if the child stopped on a signal. 
WSTOPSIG(stat_val);     If WIFSTOPPED is nonzero, this returns a signal number. 

An example code which used wait() is shown below


int main(void){
    pid_t child_pid;
    int *status=NULL;
        /* wait for child, getting  PID */
        printf("I'm the parent.\n");
        printf("My child's PID was: %d\n",child_pid);
    } else {
        printf("I'm the child.\n");
    return 0;

Or a more detailed program


int main() 

  pid_t pid; 
  int exit_code; 

  pid = fork(); 
    case -1: exit(1); 
    case 0:  exit_code = 11; //Set the child exit process
    default: exit_code = 0; 

  if (pid) 
     // This is the parent process
     int status; 
     pid_t child_pid; 

     child_pid = wait(&status); 

     printf("Child process finished with PID [%d]\n", child_pid); 
     if (WIFEXITED(status)) 
       printf("Child exited with code [%d]\n", WEXITSTATUS(status)); 
       printf("Child terminated abnormally.\n"); 

So now, whats a Zombie process?

When using fork() to create child processes it is important to keep track of these processes. For instance, when a child process terminates, an association with the parent survives untill the parent either terminates normally or calls wait(). The child process entry in the process table is not freed up immediately. Although it is no longer active, the child process is still in the system because it's exit code needs to be stored in the even the parent process calls wait(). The child process is at that point referred to as a zombie process. Note, if the parent terminates abnormally then the child process gets the process with PID 1, (init) as parent. (such a child process is often referred to as an orphan). The child process is now a zombie. It is no longer running, it's origional parent process is gone, and it has been inherited by init. It will remain in the process table as a zombie untill the next time the table is processed. If the process table is long this may take a while. till init cleans them up. As a general rule, program wisely and try to avoid zombie processes. When zobbies accumulate they eat up valuable resources.

The waitpid() system call is another call that can be used to wait for child processes. This system call however can be used to wait for a specific process to terminate.

pit_t waitpid(pid_t pid, int *status, int options); 

The pid argument specifies the PID of the particular child process to wait for. If it is a -1 then waitpid() will return information to the child process. Status information will be written to the location pointed to by status. The options argument enables us to change the behavior of waitpid(). A very usefull oprion is WNOHANG which prevents the call to waitpid() from suspending the execution of the caller. We can it to find out whether any child process has terminated and, if not, to continue.

Synchronous and asynchronous process execution

In some cases, for example if the child process is a server or "daemon" ( a process expected to run all the time in the background to deliver services such as mail forwarding) the parent process would not wait for the child to finish. In other cases, e.g. running an interactive command where it is not good design for the parent's and child's output to be mixed up into the same output stream, the parent process, e.g. a shell program, would normally wait for the child to exit before continuing. If you run a shell command with an ampersand as it's last argument, e.g. sleep 60 & the parent shell doesn't wait for this child process to finish.

So whats the difference between fork() and vfork()?

The system call vfork(), is a low overhead version of fork(), as fork() involves copying the entire address space of the process and is therefore quite expensive. The basic difference between the two is that when a new process is created with vfork(), the parent process is temporarily suspended, and the child process might borrow the parent's address space. This strange state of affairs continues until the child process either exits, or calls execve(), at which point the parent process continues. This means that the child process of a vfork() must be careful to avoid unexpectedly modifying variables of the parent process. In particular, the child process must not return from the function containing the vfork() call, and it must not call exit() (if it needs to exit, it should use _exit(); actually, this is also true for the child of a normal fork()).

However, since vfork() was created, the implementation of fork() has improved , most notably with the introduction of `copy-on-write', where the copying of the process address space is transparently faked by allowing both processes to refer to the same physical memory until either of them modify it. This largely removes the justification for vfork(); indeed, a large proportion of systems now lack the original functionality of vfork() completely. For compatibility, though, there may still be a vfork() call present, that simply calls fork() without attempting to emulate all of the vfork() semantics.

Question 2

What are turnaround time and response time?

Turnaround time is the interval between the submission of a job and its completion. Response time is the interval between submission of a request, and the first response to that request.

Question 3

What is the Translation Lookaside Buffer (TLB)?

In a cached system, the base addresses of the last few referenced pages is maintained in registers called the TLB that aids in faster lookup. TLB contains those page-table entries that have been most recently used. Normally, each virtual memory reference causes 2 physical memory accesses-- one to fetch appropriate page-table entry, and one to fetch the desired data. Using TLB in-between, this is reduced to just one physical memory access in cases of TLB-hit.

Question 4

What is cycle stealing?

We encounter cycle stealing in the context of Direct Memory Access (DMA). Either the DMA controller can use the data bus when the CPU does not need it, or it may force the CPU to temporarily suspend operation. The latter technique is called cycle stealing. Note that cycle stealing can be done only at specific break points in an instruction cycle.

Question 5

What is a reentrant program?

Re-entrancy is a useful, memory-saving technique for multiprogrammed timesharing systems. A Reentrant Procedure is one in which multiple users can share a single copy of a program during the same period.

Reentrancy has 2 key aspects:

  • The program code cannot modify itself.
  • The local data for each user process must be stored separately.

Thus, the permanent part is the code, and the temporary part is the pointer back to the calling program and local variables used by that program. Each execution instance is called activation. It executes the code in the permanent part, but has its own copy of local variables/parameters. The temporary part associated with each activation is the activation record. Generally, the activation record is kept on the stack.
Note: A reentrant procedure can be interrupted and called by an interrupting program, and still execute correctly on returning to the procedure.

Question 6

When is a system in safe state?

The set of dispatchable processes is in a safe state if there exist at least one temporal order in which all processes can be run to completion without resulting in a deadlock.

Question 7

What is busy waiting?

The repeated execution of a loop of code while waiting for an event to occur is called busy-waiting. The CPU is not engaged in any real productive activity during this period, and the process does not progress toward completion

Question 8

In the context of memory management, what are placement and replacement algorithms?

Placement algorithms determine where in the available main-memory to load the incoming process. Common methods are first-fit, next-fit, and best-fit.

Replacement algorithms are used when memory is full, and one process (or part of a process) needs to be swapped out to accommodate the new incoming process. The replacement algorithm determines which are the partitions (memory portions occupied by the processes) to be swapped out.

Question 9

What is mounting?

Mounting is the mechanism by which two different file systems can be combined together. This is one of the services provided by the operating system, which allows the user to work with two different file systems, and some of the secondary devices.

Question 10

What do you mean by dispatch latency?

The time taken by the dispatcher to stop one process and start running another process is known as the dispatch latency.

Test Your Skills Now!
Take a Quiz now
Reviewer Name