Sei sulla pagina 1di 24

Lab 1: 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 Lab1_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 Lab1_1.c :
tiger> cat Lab1_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:

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.
Example Lab1_2.c :
Each process prints a message identifying itself.
tiger> cat Lab1_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 Lab1_3.c :
Multiple forks:
tiger> cat Lab1_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 Lab1_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 Lab1_4.c
vlsi>a.out
Hello World!
I am the child process.
I am the parent process.
vlsi>
Example Lab1_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 Lab1_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 Lab1_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 Lab1_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:

> a.out & execute the program in the background


[1] 5186

> 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

> kill 5186 kill the parent process


[1] Terminated a.out
> 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 Lab1_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 Lab1_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 Lab1_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 Lab1_10.c -o myecho


tiger> myecho A K Singh
A K Singh
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 Lab1_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 Lab1_11.c -o myecho


tiger> myecho A K Singh
A K Singh
tiger>

Example Lab1_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 Lab1_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 Lab1_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 ) ;
if ( fork ( ) == 0 )
{
printf ("%d: I'm the child !\n", getpid ( ) ) ;
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 Lab1_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 Lab1_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 ( ) ) ;
}

Potrebbero piacerti anche