Lab 1
Lab 1
***********************************************************************************
***************************
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.
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.
A program usually runs as a single process. However later we will see how we can make programs run
as several separate communicating processes.
___________________________________________________________________________________
______________________________
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>)
___________________________________________________________________________________
________________________________
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.
___________________________________________________________________________________
______________________________
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 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'.
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:
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
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.
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
#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 == 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 ;
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 ( ) ) ;
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 ( ) ) ;
#include <stdio.h>
main ( )
{
int pid ;
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 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 :
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] ) ;
Sample Output:
Example Lab1_12.c :
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) ;
}
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!
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.
__________________________________________________________________________
main ( )
{
pid_t whichone, first, second ;
int howmany, status ;
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") ;
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) ;
}
}
}
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) ;
}