Thursday, February 6, 2014

PROCESSES, FORK AND WAIT

UNDERSTANDING PROCESSES

A process consist of an address space and a single thread of control that executes with that address space and its required system resources.

Wao, that quite mouthful of jargon. Speaking in simple language a process is essentially a running program, consisting of some program code, some data and variables, open files and an environment.

The UNIX system usually shares the code between processes so that there is only one copy of a programs code in memory at a time. Same is true for shared system libraries.

The processes running at a time can be viewed in the process tree maintained by UNIX. Each process has an identification number that is used to manage it as well as to index it in the tree.

When UNIX starts it runs a single program "init" which starts other system processes who themselves may start some more, gradually firing complete ensemble of OS features and facilities.

CREATING NEW PROCESSES USING system FUNCTION

We can create new processes from withins of another program. One of the ways is to use system function, which takes in a shell command as a string and tries to run it.

#include <stdlib.h>
#include <stdio.h>
int main(){
    printf("Running ls command with system\n");
    system ("ps -ax");
    printf("Done. \n");
    exit(0);
}
	  

CREATING NEW PROCESSES USING exec FUNCTION

Another method probably with better control over the child processes is provided by family of exec commands. Members of this family differ from one another in the way they start the processes or present the program arguments. But generally speaking they replace the current process with another created according to the arguments given. The program given by the path argument is used as the program code to execute in place of that which is currently running.
#include <unistd.h>
#include <stdio.h>
int main(){
printf("Running ps with execlp\n"
execlp("ps","ps","-ax",0);
printf("Done\n");
exit(0);
}
	  

This program will print the first message and then calls the execlp to execute ps command. This new process will replace our program and control would return to terminal once ps finishes. This means that subsequent lines of our code never get executed. Only time that an exec function will return is when you have an error while executing. In such case it returns a value -1 and errno is set accordingly.

CREATING NEW PROCESSES USING fork FUNCTION

The third way to create new processes is 'fork' which creates new processes without replacing the parent. This is a system call and duplicates the calling process a create a new entry in the process table with several characteristics identical to the parent.

A simple example of fork is given here:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(){
  
  /*Declare variables*/
  pid_t pid;             /*process id*/
  char* slogan;	         /*message to be printed*/
  int n;    

  /*Print a welcome message*/
  printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
  printf("(%d/%d) A program with fork application\n",getpid(),getppid());

  /*Create a new process using fork*/
  pid  = fork();
  
  /*Processes are managed using fork*/
  switch(pid){
    /*Error during fork*/
  case -1:
    exit(0);
      
    /*fork returns value zero in children*/
  case 0:
    slogan = "(%d/%d) Hello World, I am child\n";
    n=5;
    break;

    /*but not inside the parent*/
  default:	
    slogan = "(%d/%d) Hello World, This is parent\n";
    n=3;
    break;
    
  }
  /**
     THIS PART IS TEMPORARILY COMMENTED OUT
  int status;
  pid_t p = wait(&status);
  if(WIFEXITED(status)){
    printf("(%d/%d) Child exited normally w/status=%d\n",
             getpid(),getppid(),WIFEXITSTATUS());
	}
   else
   printf("(%d/%d) Child exited anormally ",
          getpid(),getppid());

   *************************************************/

  /*Now make each process print a characteristic number of times*/

  for(; n>0;n--){
    
    printf(slogan,getpid(),getppid());    
    sleep(2);
  }
  
  /*If you make upto this point, you are discharged honourably !*/
  exit(0);
  
}
	  

Now it is a time to have a brief look at manual page for the fork(2):
Library:
    #include <unistd.h>
Prototype:
    pid_t fork(void);
Description:
    1. creates  a  new  process by duplicating the calling process
    2. child has its own unique process ID
    3. parent process ID is the same as current process
    4. child does not inherit its parent's memory locks
Return:
    On Success:		
       PID of the child process is returned in the parent
       0 is returned inside the child

    On Error: returns -1, no child created and errno is set.
	  

Another function that is used in conjuction with fork is wait:
Library:
    #include <sys/types.h>
    #include <sys/wait.h>
Prototype:
    pid_t wait(int* status);
Description:
    1. wait for state changes in a child of the calling process
    2. obtain information about the child whose  state  has  changed
    Notes: What is a state change?
	1. the child terminated
	2. the child was stopped by a signal
	3. the child was resumed by a signal. 

    Notes: Why use wait?
       In the case of a terminated child, performing a
       wait  allows  the system to release the resources 
       associated with the child; if a wait is not performed, 
       then the terminated child remains in a  "zombie"  state 

Inspecting Status: The integer status is examined with following macros
       	   
   WIFEXITED(status): true if the child terminated normally.

   WEXITSTATUS(status): 
   	returns the exit status of the child.  
   	should be employed only if WIFEXITED returned true.

   WIFSIGNALED(status)
        returns true if the child process was terminated by a signal.

   WTERMSIG(status)
        returns the number of the signal that caused the child  terminate.
        should be employed only if WIFSIGNALED returned true.

   WCOREDUMP(status)
        returns  true if the child produced a core dump.
        employed if WIFSIGNALED returned true.  

   WIFSTOPPED(status)
        returns true if the child process was stopped by delivery of a signal; 

   WSTOPSIG(status)
        returns the number of the signal which caused the child to  stop.

   WIFCONTINUED(status)
        returns true if the child process was resumed by delivery of SIGCONT.
	  

The wait can be used in above program to ensure that the parent process wait for all the child processes to terminate before terminating itself. An illustration of wait function is given below. It can be run inside the program shown above (just insert it before the 'for' looping).
 int status;
  pid_t p  = wait(&status);
  if(WIFEXITED(status)){
    printf("(%d/%d) Child exited normally w/status=%d\n",
	   getpid(),getppid(),WEXITSTATUS(status));
  }
  else{
    printf("(%d/%d) Child exited anormally ", getpid(),getppid());

  }
	  

It may be useful to compare the output from this example program before and after the usage of wait function:
* Without wait:

(7025/4265) A program with fork application
(7025/4265) Hello World, This is parent
(7026/7025) Hello World, I am child
(7025/4265) Hello World, This is parent
(7026/7025) Hello World, I am child
(7025/4265) Hello World, This is parent
(7026/7025) Hello World, I am child
(7026/7025) Hello World, I am child
anil@indica$ (7026/1903) Hello World, I am child

* With wait:
(6967/4265) A program with fork application
(6968/6967) Child exited normally w/status=0
(6968/6967) Hello World, I am child
(6968/6967) Hello World, I am child
(6968/6967) Hello World, I am child
(6968/6967) Hello World, I am child
(6968/6967) Hello World, I am child
(6967/4265) Child exited normally w/status=0
(6967/4265) Hello World, This is parent
(6967/4265) Hello World, This is parent
(6967/4265) Hello World, This is parent
	  

We saw that there was a zombie process that outlived the parent process when we did not use the wait function.

SUMMARY

We have come quite a distance from our first post where we talked about the fundamentals of linux OS. The second post was about taking baby steps into I/O operations, and the third one dealt with reading and writing information from the directories. In the current post which is fourth one in this series, we talked a little bit about what a process is and how do we create the new processes from existing ones. We spent quite a bit of time on discussing fork method and did an example to demonstrate the working. Finally we had a brief idea of the usefulness of wait function, and we compared the output of our program before and after the inclusion of wait. In our next post we shall keep building these foundations and talk about file I/O in Linux System programming.

No comments:

Post a Comment