$ mkdir $HOME/process2
$ cd $HOME/process2
Bring [process2.txt] in that directory
$ ftp IP-address-of-FTP-server
[parent] process may use [wait] or [waitpid] system calls to read the
exit status of a [child] process.
If a child terminates before the parent and the parent does not read
child's exit status, the kernel does the following:
(a) Frees memory and closes opened file descriptors used by child.
(b) Saves process ID, terminating status and some other information
of the terminated child, so that the parent can read the exit
status when the parent is ready.
During this period ( the child has terminated but the parent has not
read the child's exit status and the parent is still existing ), the
child process becomes a zombie process. When the parent terminates,
the kernel knows that now, no process is going to read the exit status
of that child. So the zombie process is removed by the kernel. But if
the parent runs for a long time (such as a server process, which might
spawn children to do server tasks and terminates only when the system
is re-booted ), the zombie processes, if generated, can tie up system
resources.
The following program shows how zombie processes can cause problem. To
simulate server process, the parent enters an endless loop.
Save the following in a file [f1.c].
------------------------------------------------
// file-name f1a.c
# include
# include
# include
# include
int main(void)
{
pid_t x;
while(1) // endless loop to simulate a server
{
// parent creates a new process
x = fork();
if( x == -1 ) { perror("fork"); exit(1); }
if( x == 0 ) // child process
{
// child exits normally
exit(0);
} // end of child process block
// rest is parent process
// parent does not read the exit status of a
// child as the following line is commented
// wait( NULL );
sleep(2);
}
return 0;
}
------------------------------------------------
Compile and execute in Terminal-2
T-2 $ gcc -Wall ./f1a.c -o ./onea
T-2 $ ./onea
The process [ onea ] forks a new process, at intervals of 2 seconds.
The child processes terminate normally. But the parent does not read
the exit status of its child processes. The parent process also does
not terminate. In this situation a zombie process is created every two
seconds. If this program runs for a long time, the zombie processes
increase and computer performance degrades.
In another terminal check the processes
T-3 $ ps ax
Were a number of zombie processes created ? ( Y / n )
Terminate the program with CTRL-C.
T-2 $ CTRL-C
Were the zombie processes removed ? ( Y / n )
Uncomment the following line in [f1a.c]
//wait( NULL );
Compile and run.
T-2 $ gcc -Wall ./f1a.c -o ./onea
T-2 $ ./onea
In this program, parent reads the exit status of a child with [ wait ]
system call. Check the processes
T-3 $ ps ax
Was any zombie process created ? ( y / N )
Terminate the parent with CTRL-C.
T-2 $ CTRL-C
The program [f1a.c] is modified to show that a child process becomes a
zombie process, also on abnormal termination, if the conditions exist.
Save the following program as [f1b.c]
-------------------------------------------------
// file-name f1b.c
# include
# include
# include
int main(void)
{
pid_t x;
int n;
while(1)
{
x = fork();
if( x == -1 ) { perror("fork"); exit(1); }
if( x == 0 ) // child process
{
n = 100 / 0; // calling process gets SIGFPE
while(1) { sleep(1); }
}
sleep(2);
}
return 0;
}
-------------------------------------------------
Compile and run the program
T-2 $ gcc -Wall ./f1b.c -o ./oneb
T-2 $ ./oneb
T-3 $ ps ax
Were zombies created ? ( Y / n )
In this program child tries division by zero. The kernel detects this
and sends SIGFPE signal to that child process. The default action of
this signal is to terminate the process. This is abnormal termination
of the child.
You may check this with....
$ strace -f ./oneb
Terminate the parent with CTRL-C.
T-2 $ CTRL-C
[init] adopts an orphaned process:
When a process terminates, the
kernel checks all the existing processes to find out if the terminated
process is the parent of any existing process. In such case, the PPID
of the existing process is changed to 1 ( PID of init ). In this way
it is ensured that every process has a parent. If such a child process
terminates, it does not become zombie, because [init] arranges to read
exit status of all its children ( spawned or adopted ). The following
program illustrates that orphaned processes are adopted by [ init ].
Though [init] is the parent of these processes, the ownership and the
controlling terminal (ttyx) of these processes do not change.
------------------------------------------------------------
// file-name f2.c
#include
#include
#include
int main(void)
{
pid_t y, i;
// main (parent) creates a new process
y = fork();
if( y == -1 ) { perror("fork"); exit(1); }
if( y == 0 ) // child process
{
// child prints it's parent's ID every second
// in an endless loop
while( 1 )
{
i = getppid();
printf("child(%u): My parent is %u \n", getpid(), i );
sleep(1);
}
} // end of child process block
// rest is parent process
printf("parent: My PID = %u \n", getpid() );
// parent exits after 10 seconds
sleep(10);
exit(0); // now the child becomes orphan
}
------------------------------------------------------------
Compile and run
T-2 $ gcc -Wall ./f2.c -o ./two
T-2 $ ./two
Did [init] adopt the orphaned process ? ( Y / n )
Use the commands in another terminal and fill-in the gaps.
T-3 $ ls -l /proc/PID-of-orphaned-process/
Who was the owner of the orphaned process( two ) ? ________________
T-3 $ cat /proc/PID-of-orphaned-process/status
Who was the parent of the orphaned process( two ) ? ___
T-3 $ ps ax
What was the controlling terminal of [two] ? ____________
Terminate the orphaned process
T-3 $ kill -SIGTERM pid-of-orphaned-process
T-3 $ ps ax
Did the terminated orphaned process became a zombie ? ( y / N )
Read manual for [ wait(2) ].
The [wait] system call suspends execution of the current process until
a child has exited ( normally or abnormally ). This statement assumes
that the parent is not terminated by a signal. [wait] is not affected
if the child process is stopped. [SIGCHLD] signal is delivered to the
parent, when a child is stopped or terminated. By default, parent
ignores this signal. [wait] system call may be used in signal handler
of [SIGCHLD].
The returned value of [wait] is -1, if an error occurs. On success,
the process ID of the child which exited, is returned. [ wait ] call
returns immediately ( does not block ), with -1, if no child process
exists.
The following program illustrates that [wait] system call blocks, till
the child terminates normally or abnormally. It also shows that [wait]
returns immediately with -1, when no child process exists.
---------------------------------------------------
// file-name f3.c
#include
#include
#include
#include
int main(void)
{
pid_t i;
// main ( parent ) creates a process
i = fork();
if( i == -1 ) { perror("fork-1"); exit(1); }
if( i == 0 ) // 1st child process
{
printf("1st child: my PID = %u \n", getpid() );
sleep(30);
exit(0); // 1st child exits after 30 seconds
} // end of 1st child process block
// parent creates another process
i = fork();
if( i == -1 ) { perror("fork-2"); exit(2); }
if( i == 0 ) // 2nd child process
{
printf("2nd child: my PID = %u \n", getpid() );
sleep(20);
exit(0); // 2nd child exits after 20 seconds
} // end of 2nd child process block
// rest is parent process
// parent waits for termination of any child
i = wait( NULL );
if( i == -1 ) perror("first-wait-call");
printf("parent: child %u has exited \n", i );
// parent waits for termination of any child
i = wait( NULL );
if( i == -1 ) perror("second-wait-call");
printf("parent: child %u has exited \n", i );
// parent waits for termination of any child
i = wait( NULL );
if( i == -1 ) perror("third-wait-call");
return 0;
}
---------------------------------------------------
Compile and run
T-2 $ gcc -Wall ./f3.c -o ./three
T-2 $ ./three
Record output when the two child processes exited normally.
1st child: my PID = ________
2nd child: my PID = ________
parent: child ______ has exited
parent: child ______ has exited
third-wait-call: ____________________________
Run the following command
T-2 $ strace ./three >./tmp 2>&1
T-2 $ less ./tmp
You may search with front slash within [less]
Verify the following from the output
(a) parent process waited for the child processes to terminate.
(b) [ wait ] system call returned with -1 when there was no more child
[errno] = ECHILD (No child processes)
(c) parent received SIGCHLD signal on termination of child.
Run the program again.
T-2 $ ./three
In another terminal, terminate 1st child and 2nd child within 20
seconds
In another terminal
T-3 $ kill -9 pid-1st-child
T-3 $ kill -9 pid-2nd-child
In this experiment the child processes were terminated with signal.
Was the parent able to detect exited child processes ? ( Y / n )
[ wait ] system call also stores the exit status of the exited child
process in a specified variable. This variable may be evaluated with
the help of the following macros:
WIFEXITED(status) = non-zero for normal termination( not by signal )
WEXITSTATUS(status) = value of argument of exit() call or
value of argument of return() call; if the child
exited normally.( WIFEXITED(status) = non-zero )
WIFSIGNALED(status) = non-zero if a signal caused termination
WTERMSIG(status) = signal number that caused termination; if a signal
caused termination( WIFSIGNALED(status) = true )
The following program illustrates the above.
---------------------------------------------------------------
// file-name f4.c
#include
#include
#include
#include
int main(void)
{
pid_t i,m;
// parent creates a process
i = fork();
if( i == -1 ) { perror("fork"); exit(1); }
if( i == 0 ) // child process
{
printf( "child: my PID = %u \n", getpid() );
sleep(30);
exit(2); // child exits after 30 seconds
//exit(255);
// return 3;
} // end of child process block
// rest is parent process
// exit status of a child is stored in [m]
i = wait( &m );
if( i == -1 ) { perror("wait"); exit(3); }
printf( "parent: child %u has exited \n", i );
if( WIFEXITED(m) != 0 )
{
printf( "parent: child %u exited normally \n", i );
printf( "parent: exit status of child %u = %u \n",
i, WEXITSTATUS(m) );
}
if( WIFSIGNALED(m) != 0 )
{
printf( "parent: abnormal termination of child %u \n", i );
printf( "parent: child %u terminated by signal %u \n",
i, WTERMSIG(m) );
}
return 0;
}
---------------------------------------------------------------
Compile
T-2 $ gcc -Wall ./f4.c -o ./four
Run the program, allow it to end normally and record output
T-2 $ ./four
child: my PID = ______
parent: child ______ has exited
parent: child ______ exited normally
parent: exit status of child _____ = ____
Change exit(2) with exit(255) / return(3).
Recompile and run the program
T-2 $ gcc -Wall ./f4.c -o ./four
T-2 $ ./four
Was the parent able to determine the exit status / returned value of
the child ? ( Y / n )
Run the program again and record output in this terminal after
terminating the child with a signal from another terminal
T-2 $ ./four
T-3 $ kill -SIGTERM PID-of-child
child: my PID = _____
parent: child _____ has exited
parent: abnormal termination of child _____
parent: child ______ terminated by signal ____
If a parent has more than one child, [wait] system call returns if any
one of the children terminates. If it is required to know when a
specific child terminates, [ waitpid ] system call can be used. This
call may be used to return, when the child stops, with [ WUNTRACED ]
option. Read manual [ waitpid(2) ].
[f5.c] is a very simple example for [waitpid] system call.
---------------------------------------------------------------
// file-name f5.c
#include
#include
#include
#include
int main(void)
{
pid_t i,j,k,m;
// parent creates a process
i = fork();
if( i == -1 ) { perror("fork-1"); exit(1); }
if( i == 0 ) // 1st child process
{
printf("1st child: my PID = %u \n", getpid() );
sleep(30);
exit(0); // 1st child exits after 30 seconds
} // end of 1st child process block
// parent creates another process
j = fork();
if( j == -1 ) { perror("fork-2"); exit(2); }
if( j == 0 ) // 2nd child process
{
printf("2nd child: my PID = %u \n", getpid() );
sleep(20);
exit(22); // 2nd child exits after 20 seconds
} // end of 2nd child process block
// parent process
// parent is waiting for 2nd child. j is the PID of 2nd child
k = waitpid( j, &m, WUNTRACED );
if( k == -1 ) { perror("wait-1"); exit(3); }
printf("parent: child %u has stopped or exited \n", k );
if( WIFSTOPPED(m) != 0 )
printf("parent: child %u has stopped \n", k );
if( WIFEXITED(m) != 0 )
{
printf("parent: child %u exited normally \n", k );
printf("parent: exit status of child %u = %u \n",
k, WEXITSTATUS(m) );
}
if( WIFSIGNALED(m) != 0 )
{
printf("parent: abnormal termination of child %u \n", k );
printf("parent: child %u terminated by signal %u \n",
k, WTERMSIG(m) );
}
k = wait( NULL );
if( k == -1 ) { perror("wait-2"); exit(4); }
printf("parent: child %u has exited \n", k );
return 0;
}
---------------------------------------------------------------
Compile, run and allow to exit normally
T-2 $ gcc -Wall ./f5.c -o ./five
T-2 $ ./five
Was normal termination of 2nd child detected ? ( Y / n )
Was exit status of 2nd child determined ? ( Y / n )
Run again
T-2 $ ./five
From another terminal stop 2nd child
T-3 $ kill -SIGSTOP PID-of-2nd-child
Was stoppage of 2nd child detected ? ( Y / n )
Run again
T-2 $ ./five
From another terminal terminate 2nd child with SIGTERM
T-3 $ kill -SIGTERM PID-of-2nd-child
Was abnormal termination of 2nd child detected ? ( Y / n )
Daemon process:
It is a process without a controlling terminal( runs in
background ). Most of the servers are daemon processes. [ sendmail ]
daemon, as for example, waits for incoming [ SMTP ] connections from
clients.
The system daemons are usually started by [init] process by running
some shell scripts from [ /etc/rc.d/init.d/ ].
A daemon process may also be started by user from a terminal. In this
experiment a daemon process is started from terminal.
A typical system daemon process has the following properties ( among
others):
1. They are started when the host boots up and terminate when the
host shuts down.
2. They wait for some events to occur.
3. Sometimes they create child processes to handle service requests.
Some rules are followed to write code for a daemon process:
The daemon must not be a member of a process group in which some
other process is the session group leader. A session group leader
can send signal to all its children, thus affecting the daemon. The
usual procedure is to call [ fork] and then the parent exits. The
child then calls [ setsid ] system call to become session leader.
[ setsid ] system call fails if the calling process is already the
session leader. The [fork] system call ensures that the child is not
the session leader, when it calls [ setsid ]. Read [setsid(2)].
The child inherits opened file descriptors from parent. Unnecessary
file descriptors ( standard input, standard output, standard error
among them ) should be closed. An arrangement might be made to save
error messages suitably.
[umask] may be cleared.
For details read "Advanced Programming in the UNIX Environment" by
W.Richard Stevens.
[f6.c] is a simple example of a daemon process
--------------------------------------------------------------------
// file-name f6.c
#include
#include
#include
#include
int main(void)
{
pid_t x,y;
x = fork();
if( x == -1 ) { perror("fork-error"); exit(1); }
if ( x == 0 ) // child process.
{
printf("child: pid=%d pgrp=%d\n",getpid(),getpgrp());
y = setsid();
if( y == -1 ) { perror("setsid-error"); exit(1); }
printf("child: Now I have no controlling terminal\n");
printf("child: Now I process group leader\n");
printf("child: pid=%d pgrp=%d\n",getpid(),getpgrp());
printf("child: closing three inherited file descriptors\n");
close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO);
// this line is not going to be printed on standard output
printf("child: help ! HELP ! \n");
//umask(0000); //File creation mask may be cleared.
while( 1 ) //Endless loop to to simulate daemon process.
{
sleep(1);
}
}// child process block ends.
// rest is parent process
printf("parent: pid=%u pgrp=%u\n",getpid(),getpgrp());
printf("parent: if pid == pgrp, [setsid] call should fail\n");
y = setsid();
if( y == -1 ) { perror("parent's-setsid-call"); }
printf("parent: exiting...\n");
return 0;
} // main ends.
--------------------------------------------------------------------
Compile and run
$ gcc -Wall ./f6.c -o ./six
$ ./six
Press Enter-key
Run [ $ ps ax ]
Is there a controlling terminal for the process ? ( y / N )
Check the open file descriptors of the daemon process
$ ls -l /proc/PID-of-six/fd
The daemon should have no open file descriptors.
Terminate the daemon process
$ kill -SIGKILL PID-of-six
Using CTRL-C (a terminal generated signal) would not be effective, as
a daemon process has no controlling terminal.
------------------
