ICS 431  Operating Systems

Lab 8:    Process Creation and Execution **************************************************************************************************************

Objective:

This lab describes how a program can create, terminate, and control child processes. Actually, there are three distinct operations involved: creating a new child process, causing the new process to execute a program, and coordinating the completion of the child process with the original program

 

***************************************************************************************************

    

What is a process? :

 

A process is basically a single running program. It may be a ``system'' program (e.g login, update, csh) or program initiated by the user (pico, a.out or a user written one). 

When UNIX runs a process it gives each process a unique number - a process ID, pid

The UNIX command ps will list all current processes running on your machine and will list the pid

The C function int getpid( ) will return the pid of process that called this function. 

 

Processes are the primitive units for allocation of system resources. Each process has its own address space and (usually) one thread of control. A process executes a program; you can have multiple processes executing the same program, but each process has its own copy of the program within its own address space and executes it independently of the other copies. 

Processes are organized hierarchically. Each process has a parent process which explicitly arranged to create it. The processes created by a given parent are called its child processes. 

 

A child inherits many of its attributes from the parent process. 

 

Every process in a UNIX system has the following attributes:

·         some code

·         some data

·         a stack

·         a unique process id number (PID)

 

When UNIX is first started, there’s only one visible process in the system. This process is called “init”, and its PID is 1. The only way to create a new process in UNIX is to duplicate an existing process, so “init” is the ancestor of all subsequent processes. When a process duplicates, the parent and child processes are identical in every way except their PIDs; the child’s code, data, and stack are a copy of the parent’s, and they even continue to execute the same code. A child process may, however, replace its code with that of another executable file, thereby differentiating itself from its parent. For example, when “init” starts executing, it quickly duplicates several times. Each of the duplicate child processes then replaces its code from the executable file called “getty” which is responsible for handling user logins.

When a child process terminates, its death is communicated to its parent so that the parent may take some appropriate action.

A process that is waiting for its parent to accept its return code is called a zombie process.

If a parent dies before its child, the child (orphan process) is automatically adopted by the original “init” process whose PID is 1.

Its very common for a parent process to suspend until one of its children terminates. For example, when a shell executes a utility in the foreground, it duplicates into two shell processes; the child shell process replaces its code with that of utility, whereas the parent shell waits for the child process to terminate. When the child process terminates, the original parent process awakens and presents the user with the next shell prompt.

 

Here’s an illustration of the way that a shell executes a utility:

 

 

A program usually runs as a single process. However later we will see how we can make programs run as several separate communicating processes.  

_________________________________________________________________________________________________________________

 

Running UNIX commands from C :


We can run commands from a C program just as if they were from the UNIX command line by using the system( ) function.  

int   system  ( char *string ) -- where string can be the name of a UNIX utility, an executable shell script or a user program. System returns the exit status

of the shell. System is prototyped in <stdlib.h> 

Example: Call ls from a program 

 

File Lab8_0.c :


main( )
{     printf(``Files in Directory are:n'');
       system(``ls -l'');
}

system is a call that is made up of 3 other system calls: execl( ), wait( ) and fork( ) (which are prototyed in <unistd>) 

 

___________________________________________________________________________________________________________________

 

Command Line Arguments :

In the Unix environment, arguments can be passed to the main function of a C program much like parameters can be passed to other functions in C. The arguments are passed as strings to the main function. The syntax for the main function prototype in this case is as follows:       

 

int  main (argc, argv)

int argc;

char *argv[ ];

where argc is the number of command line arguments, including the command name, and argv[i] is a pointer to the ith argument which is represented as a character string.

Example

For a program named prog1 which is invoked with the command line:

prog1 add 12 5

the value of argc is 4, the value of argv[0] is "prog1", the value of argv[1] is "add", the value of argv[2] is "12", and the value of argv[3] is "5".

Note that all arguments are represented as character strings. If numbers are to be passed into main, they must be converted from strings inside main.

_________________________________________________________________________________________________________________

 

Process Creation Concepts :


This section gives an overview of processes and of the steps involved in creating a process and making it run another program. 

Each process is named by a process ID number. A unique process ID is allocated to each process when it is created. The lifetime of a process ends when

its termination is reported to its parent process; at that time, all of the process resources, including its process ID, are freed. 

Processes are created with the fork system call (so the operation of creating a new process is sometimes called forking a process). The child process

created by fork is a copy of the original parent process, except that it has its own process ID

After forking a child process, both the parent and child processes continue to execute normally. 

 

If you want your program to wait for a child process to finish executing before continuing, you must do this explicitly after the fork operation, by calling wait

or waitpid. These functions give you limited information about why the child terminated--for example, its exit status code. 

A newly forked child process continues to execute the same program as its parent process, at the point where the fork call returns. You can use

the return value from fork to tell whether the program is running in the parent process or the child

Having several processes run the same program is only occasionally useful. But the child can execute another program using one of the exec functions. The program that the process is executing is called its process image. Starting execution of a new program causes the process to forget all about its previous process image; when the new program exits, the process exits too, instead of returning to the previous process image. 

___________________________________________________________________________________________________________________


Process Identification :

The pid_t data type represents process IDs. You can get the process ID of a process by calling getpid. The function getppid returns the process ID of the

parent of the current process (this is also known as the parent process ID). Your program should include the header files `unistd.h' and `sys/types.h' to use

these functions. 

Data Type: pid_t 


The pid_t data type is a signed integer type which is capable of representing a process ID. In the GNU library, this is an int

Function: pid_t    getpid (void) 


The getpid function returns the process ID of the current process. 

Function: pid_t    getppid (void) 


The getppid function returns the process ID of the parent of the current process. 

____________________________________________________________________________________________________________


Creating Multiple Processes :


A special type of process important in the Unix environment is the daemon

 

A daemon is a process that waits for a user to request some action, and then performs the request. As an example, there is an ftp daemon that waits

until a user requests that a file be transferred and then performs the request. This implies that the daemon must always be ready to receive requests at the same

time it is transferring files. To solve this problem, the daemon can create an identical process (a child process) to handle the file transfer while the

 original process (the parent process) continues to wait for requests. The fork system call is used by a process to spawn a process identical to itself.

 

The fork function is the primitive for creating a process. It is declared in the header file `unistd.h'. 

 

Function: pid_t    fork (void) 


The fork function creates a new process

If the operation is successful, there are then both parent and child processes and both see fork return, but with different values: it returns a value of 0 in

the child process and returns the child's process ID in the parent process. 

If process creation failed, fork returns a value of -1 in the parent process and no child is created.

 

The specific attributes of the child process that differ from the parent process are: 

 

The child process has its own unique process ID. 

The parent process ID of the child process is the process ID of its parent process. 

The child process gets its own copies of the parent process's open file descriptors. Subsequently changing attributes of the file descriptors in the parent

process won't affect the file descriptors in the child, and vice versa. However, the file position associated with each descriptor is shared by both processes.

The elapsed processor times for the child process are set to zero.

The child doesn't inherit file locks set by the parent process. 

The child doesn't inherit alarms set by the parent process. 

The set of pending signals for the child process is cleared. 

Example Lab8_1.c :

 tiger> cat Lab8_1.c

#include <stdio.h>

#include <unistd.h>  /* contains fork prototype */

int main(void) {

printf("Hello World!\n");

fork( );

printf("I am after forking\n");

printf("\tI am process %d.\n", getpid( ));

} 

Sample output :

 

 tiger> a.out

Hello World!

I am after forking

        I am process 23848.

I am after forking

        I am process 23847.     

 

When this program is executed, it first prints Hello World! . When the fork is executed, an identical process called the child is created. Then both the parent and the child process begin execution at the next statement. Note the following:

 

When a fork is executed, everything in the parent process is copied to the child process. This includes variable values, code, and file descriptors.

Following the fork, the child and parent processes are completely independent.

There is no guarantee which process will print I am a process first.

The child process begins execution at the statement immediately after the fork, not at the beginning of the program.

A parent process can be distinguished from the child process by examining the return value of the fork call. Fork returns a zero to the child process and the process id of the child process to the parent.

A process can execute as many forks as desired. However, be wary of infinite loops of forks (there is a maximum number of processes allowed for a single user).

Example Lab8_2.c :

Each process prints a message identifying itself.

 

 tiger> cat Lab8_2.c

#include <stdio.h>

#include <unistd.h>/* contains fork prototype */

int main(void)

{

int pid;

printf("Hello World!\n");

printf("I am the parent process and pid is : %d .\n",getpid());

printf("Here i am  before use of forking\n");

pid = fork( );

printf("Here I am just after forking\n");

if (pid == 0)

printf("I am the child process and pid is :%d.\n",getpid());

else

printf("I am the parent process and pid is: %d .\n",getpid());

}  

 

Sample Output :

 

 tiger> a.out

 

Hello World!

I am the parent process and pid is : 23951 .

Here i am before use of forking

Here I am just after forking

I am the child process and pid is :23952.

Here I am just after forking

I am the parent process and pid is: 23951 .      

Example Lab8_3.c :

Multiple forks:

 

   tiger> cat Lab8_3.c

#include <stdio.h>

#include <unistd.h>  /* contains fork prototype */

int main(void)

{

printf("Here I am just before first forking statement\n");

 

fork( );

printf("Here I am just after first forking statement\n");

fork( );

printf("Here I am just after second forking statement\n");

fork( );

printf("Here I am just after third  forking statement\n");

printf("    Hello World from process %d!\n", getpid( ));

}

         

Sample Output :

 

tiger> a.out

 

Here I am just before first forking statement

Here I am just after first forking statement

Here I am just after second forking statement

Here I am just after third  forking statement

   Hello World from process 24120!

Here I am just after first forking statement

Here I am just after second forking statement

Here I am just after third  forking statement

    Hello World from process 24119!

Here I am just after second forking statement

Here I am just after third  forking statement

    Hello World from process 24122!

Here I am just after third  forking statement

    Hello World from process 24123!

Here I am just after second forking statement

Here I am just after third  forking statement

    Hello World from process 24118!

Here I am just after third  forking statement

    Hello World from process 24121!

Here I am just after third  forking statement

    Hello World from process 24124!

Here I am just after third  forking statement

    Hello World from process 24117!   

 

 

 

Process Completion :


The functions described in this section are used to wait for a child process to terminate or stop, and determine its status. These functions are declared in the

 header file `sys/wait.h'. 

 

Function: pid_t   wait (int  *status-ptr)

 

wait( ) will force a parent process to wait for a child process to stop or terminate. wait ( ) return the pid of the child or -1 for an error. The exit status of the

child is returned to status_ptr

 

Function: void exit (int status) 

 

exit ( ) terminates the process which calls this function and returns the exit status value. Both UNIX and C (forked) programs can read the status value. 

By convention, a status of 0 means normal termination any other value indicates an error or unusual occurrence. Many standard library calls have

 errors defined in the sys/stat.h header file. We can easily derive our own conventions. 

If the child process must be guaranteed to execute before the parent continues, the wait system call is used. A call to this function causes the parent process to wait until one of its child processes exits. The wait call returns the process id of the child process, which gives the parent the ability to wait for a particular child process to finish.

sleep

A process may suspend for a period of time using the sleep command

 

Function: unsigned int  sleep (seconds)

 
 
 Example Lab8_4.c :

Guarantees the child process will print its message before the parent process.

 

#include <stdio.h>

#include <sys/wait.h> /* contains prototype for wait */

 

int main(void)

{

int pid;

int status;

printf("Hello World!\n");

 

pid = fork( );

 

if (pid == -1) /* check for error in fork */

{

perror("bad fork");

exit(1);

}

 

if (pid == 0)

printf("    I am the child process.\n");

else

{

wait(&status);  /* parent waits for child to finish */

printf("I am the parent process.\n");

}

}

 

Sample Output :

 

vlsi>gcc Lab8_4.c

vlsi>a.out

Hello World!

    I am the child process.

I am the parent process.

vlsi>

 

 

 Example Lab8_5.c :

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/wait.h>

 

main ( )

{

    int  forkresult ;

 

    printf ("%d:  I am the parent. Remember my number!\n", getpid( ) ) ;

    printf ("%d:  I am now going to fork ... \n", getpid( ) ) ;

 

    forkresult = fork ( ) ;

 

    if (forkresult != 0)

     {    /* the parent will execute this code */

            printf ("%d:  My child's pid is %d\n", getpid ( ), forkresult ) ;

      }

       else    /* forkresult == 0 */

        {        /* the child will execute this code */

             printf ("%d:  Hi!  I am the child.\n", getpid ( ) ) ;

                

        }

    

        printf ("%d:  like father like son. \n", getpid ( ) ) ;

}

 

Sample Output :

 

vlsi>gcc Lab8_5.c

vlsi>a.out

28715:  I am the parent. Remember my number!

28715:  I am now going to fork ...

28716:  Hi!  I am the child.

28716:  like father like son.

28715:  My child's pid is 28716

28715:  like father like son.

vlsi>  

 

_______________________________________________________________________________________________________________

Orphan processes  :

When a parent dies before its child, the child is automatically adopted by the original “init” process whose PID is 1. To, illustrate this insert a sleep statement into the child’s code. This ensured that the parent process terminated before its child.

Example Lab8_6.c :

#include <stdio.h>

main ( )

{

    int   pid ;

 

    printf ("I'am the original process with PID %d and PPID %d.\n",

                         getpid ( ), getppid ( ) ) ;

 

    pid = fork ( ) ;                                 /* Duplicate. Child and parent continue from here */

    if ( pid != 0 )            /* pid is non-zero, so I must be the parent */

    {

        printf ("I'am the parent process with PID %d and PPID %d.\n",

               getpid ( ), getppid ( ) ) ;

                        printf ("My child's PID is %d\n", pid ) ;

            }

    else                         /* pid is zero, so I must be the child */

    {

        sleep (4) ;                        /* make sure that the parent terminates first */

        printf ("I'am the child process with PID %d and PPID %d.\n",

                     getpid ( ), getppid ( ) ) ;

     }

printf ("PID %d terminates.\n", getpid ( ) ) ;

}

 

The output is:

I'am the original process with PID 5100 and PPID 5011.

I'am the parent process with PID 5100 and PPID 5011.

My child's PID is 5101

PID 5100 terminates.    /* Parent dies */

I'am the child process with PID 5101 and PPID 1.    

                /* Orphaned, whose parent process is “init” with pid 1 */

PID 5101 terminates.

________________________________________________________________________________________ 

Zombie processes  :

A process that terminates cannot leave the system until its parent accepts its return code. If its parent process is already dead, it’ll already have been adopted by the “init” process, which always accepts its children’s return codes. However, if a process’s parent is alive but never executes a wait ( ), the process’s return code will never be accepted and the process will remain a zombie.

The following program created a zombie process, which was indicated in the output from the ps utility. When the parent process is killed, the child was adopted by “init” and allowed to rest in peace.

Example Lab8_7.c :

 

#include <stdio.h>

main ( )

{

              int   pid ;

 

  pid = fork ( )          /* Duplicate. Child and parent continue from here */

 

  if ( pid != 0 )  /* pid is non-zero, so I must be the parent */

  {

         while (1) /* Never terminate and never execute a wait ( ) */

            sleep (100) ;    /* stop executing for 100 seconds */

               }

   else                          /* pid is zero, so I must be the child */

   {

               exit (42) ;     /* exit with any number */

   }

}

 

The output is:

 

mango.ccse.kfupm.edu.sa> a.out   &   execute the program in the background

[1] 5186          

 

mango.ccse.kfupm.edu.sa> ps  obtain process status

  PID TT STAT   TIME COMMAND

 5187 p0 Z      0:00 <exiting> the zombie child process

 5149 p0 S      0:01 -csh (csh)             the shell

 5186 p0 S      0:00 a.out                    the parent process

 5188 p0 R      0:00 ps

 

mango.ccse.kfupm.edu.sa> kill 5186      kill the parent process

[1]    Terminated           a.out

 

mango.ccse.kfupm.edu.sa> ps  notice that the zombie is gone now

  PID TT STAT   TIME COMMAND

 5149 p0 S      0:01 -csh (csh)

 5189 p0 R      0:00 ps
________________________________________________________________________________________


Executing a file :

 

This section describes the exec family of functions, for executing a file as a process image. You can use these functions to make a child process execute a new program after it has been forked

The functions in this family differ in how you specify the arguments, but otherwise they all do the same thing. They are declared in the header file

 `unistd.h'.

 

Function: int   execv ( const char *filename,  char *const argv[ ] ) 

 

The execv function executes the file named by filename as a new process image. 

The argv argument is an array of null-terminated strings that is used to provide a value for the argv argument to the main function of the program to be

executed. The last element of this array must be a null pointer. By convention, the first element of this array is the file name of the program sans directory

names. 


The environment for the new process image is taken from the environ variable of the current process image. 

 

Function: int   execl (const char *filename,  const char *arg0, ...)

 
This is similar to execv, but the argv strings are specified individually instead of as an array. A null pointer must be passed as the last such argument. 

Function: int   execvp (const char *filename,  char *const argv[ ] ) 


The execvp function is similar to execv, except that it searches the directories listed in the 

PATH environment variable to find the full file name of a file from filename if filename
does not contain a slash. 

This function is useful for executing system utility programs, because it looks for them in the places that the user has chosen. Shells use it to run the commands

that users type. 

Function: int   execlp (const char *filename, const char *arg0, ...) 


This function is like execl, except that it performs the same file name searching as the execvp function. 

 

These functions normally don't return, since execution of a new program causes the currently executing program to go away completely. A value of -1 is

returned in the event of a failure

 

If execution of the new file succeeds, it updates the access time field of the file as if the file had been read.

 

Executing a new process image completely changes the contents of memory, copying only the argument and environment strings to new locations.

But many other attributes of the process are unchanged: 

 

The process ID and the parent process ID. 

Session and process group membership. 

Real user ID and group ID, and supplementary group IDs. 

Current working directory and root directory. In the GNU system, the root directory is not copied when executing a setuid program; instead the system

default root directory is used for the new program.

File mode creation mask. 

Process signal mask.

Pending signals.

Elapsed processor time associated with the process; see section Processor Time. 

 

The following programs execs the commands "ls -l -a" and "echo hello there" using the 4 most-used forms of exec. Enter each, compile, and run. 

Using execl( ) : The version will not search the path, so the full name of the executable file must be given. Parameters to main() are listed as arguments to

 execl()

 Example Lab8_8.c :

 

#include <stdio.h>
#include <unistd.h>

main ( )
{
       
execl ("/bin/ls",         /* program to run - give full path */
                    "ls",                 /* name of program sent to argv[0] */
                    "-l",                 /* first parameter (argv[1])*/
                    "-a",                 /* second parameter (argv[2]) */
                    NULL) ;             /* terminate arg list */
        

    printf ("EXEC Failed\n") ;       

        /* This above line will be printed only on error and not otherwise */

 

Using execlp( ) : The version searches the PATH, so the full name of the executable file need not be given (if it is on the path). Parameters to main() are listed

as arguments to execl()

 

Example Lab8_9.c :                               

 

#include <stdio.h>
#include <unistd.h>

main ( )
{
       
execlp ("ls",         /* program to run - PATH Searched */
                      "ls",                 /* name of program sent to argv[0] */
                      "-l",                 /* first parameter (argv[1])*/
                      "-a",                 /* second parameter (argv[2]) */
                      NULL) ;             /* terminate arg list */
        

    printf ("EXEC Failed\n") ;       

         /* This above line will be printed only on error and not otherwise */

 

Using execv( ) : The version will not search the path, so the full name of the executable file must be given. Parameters to main() are passed in a single array of character pointers.  

Example Lab8_10.c :

 

#include <stdio.h>
#include <unistd.h>

main (argc, argv )

int argc ;

char *argv[ ] ;
{
       
execv ("/bin/echo",         /* program to load - full path only */
                       &argv[0] ) ;            
        

    printf ("EXEC Failed\n") ;       

        /* This above line will be printed only on error and not otherwise */
}

 

Sample Output :

 

tiger> gcc Lab8_10.c -o myecho

tiger> myecho Assalamu Alaikum

Assalamu Alaikum

tiger> 

 

 

Using execvp( ) : The version searches the path, so the full name of the executable need not be given. Parameters to main() are passed in a single array of

character pointers. This is the form used inside a shell! 

Example Lab8_11.c :

 

#include <stdio.h>
#include <unistd.h>

main (argc, argv )

int argc ;

char *argv[ ] ;
{
       
execvp ("echo",         /* program to load - PATH searched */
                       &argv[0] ) ;          

              

    printf ("EXEC Failed\n") ;   

        /* This above line will be printed only on error and not otherwise */            
}

 

Sample Output:

 

tiger> gcc Lab8_11.c -o myecho

tiger> myecho Assalamu Alaikum

Assalamu Alaikum

tiger>            

 

 Example Lab8_12.c :

 

Write a program where a child is created to execute a command.

 

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/wait.h>

 

main ( )

{

    int  forkresult ;

 

    printf ("%d:  I am the parent. Remember my number!\n", getpid( ) ) ;

    printf ("%d:  I am now going to fork ... \n", getpid( ) ) ;

 

    forkresult = fork ( ) ;

 

    if (forkresult != 0)

     {    /* the parent will execute this code */

            printf ("%d:  My child's pid is %d\n", getpid ( ), forkresult ) ;

      }

       else    /* forkresult == 0 */

        {        /* the child will execute this code */

             printf ("%d:  Hi !  I am the child.\n", getpid ( ) ) ;

             printf ("%d:  I'm now going to exec ls!\n\n\n", getpid ( ) ) ;

             execlp ("ls", "ls", NULL) ;

             printf ("%d:  AAAAH ! ! My EXEC failed ! ! ! !\n", getpid ( ) ) ;

             exit (1) ;

        }

    

        printf ("%d:  like father like son. \n", getpid ( ) ) ;

}

 

Sample Output:

 

tiger> gcc Lab8_12.c

tiger> a.out

24639:  I am the parent. Remember my number!

24639:  I am now going to fork ...

24640:  Hi !  I am the child.

24640:  I'm now going to exec ls!

 

 

24639:  My child's pid is 24640

24639:  like father like son.

tiger> a.out      Lab8_1.c   Lab8_11.c  Lab8_2.c   Lab8_8.c   myecho

Lab8_0.c   Lab8_10.c  Lab8_12.c  Lab8_3.c   Lab8_9.c

 

tiger>   

 

Run this program several times. You should be able to get different ordering of the output lines (sometimes the parent finished before the child, or vice versa).

This means that after the fork, the two process are no longer synchronized. 

______________________________________________________________________________________________________________

 

Process Completion Status :


If the exit status value of the child process is zero, then the status value reported by wait is also zero. You can test for other kinds of information encoded in

 the returned status value using the following macros. These macros are defined in the header file `sys/wait.h'. 

Macro: int WIFEXITED (int status) 
This macro returns a nonzero value if the child process terminated normally with exit

Macro: int WEXITSTATUS (int status) 
If WIFEXITED is true of status, this macro returns the low-order 8 bits of the exit status value from the child process. 

Macro: int WIFSIGNALED (int status) 
This macro returns a nonzero value if the child process terminated because it received a signal that was not handled.

Macro: int WTERMSIG (int status) 
If WIFSIGNALED is true of status, this macro returns the signal number of the signal that terminated the child process. 

Macro: int WCOREDUMP (int status) 
This macro returns a nonzero value if the child process terminated and produced a core dump

Macro: int WIFSTOPPED (int status) 
This macro returns a nonzero value if the child process is stopped

Macro: int WSTOPSIG (int status) 
If WIFSTOPPED is true of status, this macro returns the signal number of the signal that caused the child process to stop. 

 

Here's a program which forks. The parent waits for the child. The child asks the user to type in a number from 0 to 255 then exits, returning that number as

status

 Example Lab8_13.c :

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/wait.h>

 

main ( )

{

    int    number=0, statval ;

    printf ("%d:  I'm the parent !\n", getpid ( ) ) ;

    printf ("%d:  number = %d\n", getpid ( ), number ) ;

    printf ("%d:  forking ! \n", getpid ( ) ) ;

 

    if ( fork ( ) == 0 )

    {

            printf ("%d: I'm the child !\n", getpid ( ) ) ;

            printf ("%d: number = %d\n", getpid ( ), number ) ;

            printf ("%d: Enter a number :  ", getpid ( ) ) ;

            scanf ("%d", &number) ;

            printf ("%d: number = %d\n", getpid ( ), number ) ;

            printf ("%d: exiting with value %d\n", getpid ( ), number ) ;

            exit (number) ;

    }

 

    printf ("%d: number = %d\n", getpid ( ), number ) ;

    printf ("%d: waiting for my kid !\n", getpid ( ) ) ;

    wait (&statval) ;

 

    if ( WIFEXITED (statval) )

    {

           printf ("%d: my kid exited with status %d\n",

                        getpid ( ), WEXITSTATUS (statval) ) ;

    }

    else

    {

            printf ("%d: My kid was killed off ! ! \n", getpid ( ) ) ;

    }

 

Here's an example call to wait(): a program spawns two children, then waits for their completion and behaves differently according to which one is finished. Try to compile and execute it. 

Example Lab8_14.c :

#include <sys/types.h>

#include <sys/wait.h>

#include <stdio.h>

 

main ( )

{

     pid_t  whichone, first, second ;

     int  howmany, status ;

 

     if ( (first = fork ( ) ) == 0 )           /* Parent spawns 1st child */

     {

          printf ("Hi, I am the first child, and my ID is %d\n", getpid ( ) ) ;

          sleep (10) ;

          exit (0) ;

     }

     else if (first == -1)

     {

          perror ("1st fork: something went wrong\n") ;

          exit (1) ;

     }

     else if ( ( second = fork ( ) ) == 0 )    /* Parent spawns 2nd child */

     {

         

          printf ("Hi, I am the second child, and my ID is %d\n", getpid ( ) ) ;

          sleep (15) ;

          exit (0) ;

      }

     else if (second == -1)

     {

          perror ("2nd fork: something went wrong\n") ;

          exit (1) ;

     }

 

     printf ("This is parent\n") ;

 

     howmany = 0 ;

     while (howmany < 2)                  /* Wait Twice */

     {

           whichone = wait(&status) ;

           howmany++ ;

     

     if (whichone == first)

          printf ("First child exited\n") ;

     else

          printf ("Second child exited\n") ;

 

     if ( (status & 0xffff) == 0 )

         printf ("correctly\n") ;

     else

        printf ("uncorrectly\n") ;

   }

}

The first part of this example, up to the howmany=0 statement, contains nothing new: just make sure you understand what the instruction flow is in the parent and in the children. The parent then enters a loop waiting for the children's completion. The wait() system call blocks the caller process until one of its immediate children (not children's children, or other siblings) terminates, and then returns the pid of the terminated process. The argument to wait() is the address on an integer variable or the NULL pointer. If it's not NULL, the system writes 16 bits of status information about the terminated child in the low-order 16 bits of that variable. Among these 16 bits, the higher 8 bits contain the lower 8 bits of the argument the child passed to exit(), while the lower 8 bits are all zero if the process exited correctly, and contain error information if not. Hence, if a child exits with 0 all those 16 bits are zero. To reveal if this is actually the case we test the bitwise AND expression (status & 0xffff), which evaluates as an integer whose lower 16 bits are those of status, and the others are zero. If it evaluates to zero, everything went fine, otherwise some trouble occurred. Try changing the argument passed to exit() in one of the children.

___________________________________________________________________________________

Process Groups :

The setpgrp ( ) System Call

The setpgrp() system call creates a new process group. The setpgid() system call adds a process to a process group.

The synopsis for setpgrp() follows:

 

#include <sys/types.h>
#include <unistd.h>

     pid_t    setpgrp (void);
     int   setpgid (pid_t pid, pid_t pgid);

If the process calling setpgrp() is not already a session leader, the process becomes one by setting its GID to the value of its PID. setpgid() sets the process group ID of the process with PID pid to pgid. If pgid is equal to pid then the process becomes the group leader. If pgid is not equal to pid , the process becomes a member of an existing process group. 

Compile and Run the following program to understand the process groups creation.

Example Lab8_15.c :

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/wait.h>
#include <unistd.h>

#include <errno.h>

main ( )

{

   pid_t    parent_pid, child_pid, fork_pid, wait_pid ;

   pid_t    parent_grp, child_grp, grpid ;

   int         child_stat, exit_val ;

 

   exit_val = 10 ;

   parent_pid = getpid ( ) ;

   parent_grp = getpgrp ( ) ;

   printf ("\nParent process:   process ID: %ld     group ID: %ld\n",

                 (long) parent_pid, (long) parent_grp) ;

 

   fork_pid = fork ( ) ;

 

   switch (fork_pid)

   {

       case -1:

          perror ("FORK FAILED\n") ;

          errno = 0 ;

          break ;

       case 0:

          child_pid = getpid ( ) ;

          child_grp = getpgrp ( ) ;

          printf ("Child process:   process ID: %ld    group ID: %ld   "

                         "parent process ID: %ld\n", (long) child_pid,

                         (long) child_grp, (long) getppid ( ) ) ;

          grpid = setpgrp ( ) ;                        /* Change the group of child */
         
setpgid (child_pid, grpid) ;
         
child_grp = getpgrp ( ) ;
          printf ("Child process again: process ID: %ld group ID: %ld "
                        "parent process ID: %ld\n", (long)
child_pid,
                        (long)
child_grp, (long) getppid ( ) ) ;

          printf ("Child process:  terminate with \"exit\" - value:  %d\n",

                         exit_val) ;

          exit (exit_val) ;

          break ;

     default:

          printf ("Parent process: child process with ID %ld created.\n",

                         (long) fork_pid) ;

          wait_pid = wait (&child_stat) ;

          if (wait_pid == -1)

          {

              perror ("wait") ;

              errno = 0 ;

          }

          else

          {

               printf ("Parent process: child process %ld has terminated.\n",

                            (long) wait_pid) ;

           }

    }

}

 Important points to note:

 

1. The Shell acts as the parent process. All the processes started by the user are treated as the children of shell.

 

2. The status of a UNIX process is shown as the second column of the process table when viewed by the execution of the ps command. Some of the

 states are R: running, O: orphan, S: sleeping, Z: zombie.

 

3. The child process is given the time slice before the parent process. This is quite logical. For example, we do not want the process started by us to

 wait until its parent, which is the UNIX shell finishes. This will explain the order in which the print statement is executed by the parent and the children.

 

4. The call to the wait ( ) function results in a number of actions. A check is first made to see if the parent process has any children. If it does not, a -1 is

returned by wait ( ). If the parent process has a child that has terminated (a zombie), that child's PID is returned and it is removed from the process table.

However if the parent process has a child that is not terminated, it (the parent) is suspended till it receives a signal. The signal is received as soon as a child

dies.

 

Assignment:

Execute the C programs given in the following problems. Observe and Interpret the results. You will learn about child and parent processes,

and much more about UNIX processes in general by performing the suggested experiments. UNIX Calls used in the following problems:

getpid( ), getppid( ), sleep( ), fork( ), and wait( ).

 

1) Run the following program twice. Both times as a background process, i.e., suffix it with an ampersand "&". Once both processes are running as

background processes, view the process table using ps -l UNIX command. Observe the process state, PID (process ID) etc. Repeat this

experiment to observe the changes, if any. Write your observation about the Process ID and state of the process.

main ( ) {

    printf ("Process ID is: %d\n", getpid( ) ) ;

    printf ("Parent process ID is: %d\n", getppid( ) ) ;

    sleep (60) ;

    printf ("I am awake. \n");

}

 

2) Run the following program and observe the number of times and the order in which the print statement is executed. The fork ( ) creates a

 child that is a duplicate of the parent process. The child process begins from the fork ( ). All the statements after the call to fork ( ) are executed

by the parent process and also by the child process.  Draw a family tree of processes and explain the results you observed.

main ( ) {

    fork ( ) ;

    fork ( ) ;

    printf ("Parent Process ID is %d\n", getppid ( ) ) ;

}

 

3)

Run the following program and observe the result of time slicing used by UNIX.

main ( )  {

    int i=0, j=0, pid, k, x ;

    pid = fork ( );

    if ( pid == 0 ) {

        for ( i = 0; i < 20; i++ ) {

            for (k = 0; k < 10000; k++ );

             printf ("Child: %d\n", i) ;

        }

    }

    else {

        for ( j = 0; j < 20; j++ ){

              for (x = 0; x < 10000; x++ );

              printf ("Parent: %d\n", j) ;

        }

    }

}

 

4)

Run the following program and observe the result of synchronization using wait ( ).

main ( )  {

    int i=0, j=0, pid, k, x ;

    pid = fork ( );

    if ( pid == 0 ) {

        for ( i = 0; i < 20; i++ ) {

            for (k = 0; k < 10000; k++ );

             printf ("Child: %d\n", i) ;

        }

        printf ("**** Child ends **** \n") ;

    }

    else {

        wait (0) ;

        printf ("**** Parent resumes **** \n") ;

        for ( j = 0; j < 20; j++ ){

              for (x = 0; x < 10000; x++ );

              printf ("Parent: %d\n", j) ;

        }

    }

}

 

5)

Run the program several times. You should always see the same order of finish since the processes are now synchronized by wait( )

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/wait.h>

 

main ( )

{

    int  result, waitedfor, statvar ;

    printf ("%d:  I am the parent. \n", getpid( ) ) ;

    printf ("%d:  I am now going to fork ... \n", getpid( ) ) ;

 

    result = fork ( ) ;

 

    if (result != 0)

     {    /* the parent will execute this code */

            printf ("%d:  My child's pid is %d\n", getpid ( ), result ) ;

      }

       else    /* forkresult == 0 */

        {        /* the child will execute this code */

             printf ("%d:  Hi !  I am the child.\n", getpid ( ) ) ;

             printf ("%d:  I'm now going to exec ls!\n\n", getpid ( ) ) ;

             execlp ("ls", "ls", NULL) ;

             printf ("%d:  ! ! My EXEC failed ! ! ! !\n", getpid ( ) ) ;

             exit (1) ;

        }

 

        waitedfor = wait (&statvar) ;

        printf ("\n\n%d:  I waited for process %d\n", getpid ( ), waitedfor ) ;

        if (waitedfor == result)

             printf ("%d:  Yep!  That's my boy ! \n", getpid ( ) ) ;

        else

             printf ("%d:  Huh ! This is NOT my kid ! \n", getpid ( ) );

        printf ("%d:  like father like son. \n", getpid ( ) ) ;

}