This tutorial will cover the creation of child processes and process control
using fork, exec and other C library function calls using the GNU "C" compiler
on the Linux operating system.
The fork() system call will spawn a new child process which
is an identical process to the parent except that has a new
system process ID. The process is copied in memory from the parent and a new
process structure is assigned by the kernel. The return value of the
function is which discriminates the two threads of execution. A zero is
returned by the fork function in the child's process.
The environment, resource limits, umask, controlling terminal, current working
directory, root directory, signal masks and other process resources are also
duplicated from the parent in the forked child process.
Example:
#include <iostream> #include <string>
// Required by for routine #include <sys/types.h> #include <unistd.h>
using namespace std;
int globalVariable = 2;
main() { string sIdentifier; int iStackVariable = 20;
pid_t pID = fork(); if (pID == 0) // child { // Code only executed by child process
sIdentifier = "Child Process: "; globalVariable++; iStackVariable++; } else if (pID < 0) // failed to fork { cerr << "Failed to fork" << endl; exit(1); // Throw exception } else // parent { // Code only executed by parent process
Parent Process: Global variable: 2 Stack variable: 20 Child Process: Global variable: 3 Stack variable: 21
[Potential Pitfall]: Some memory duplicated by
a forked process such as file pointers, will cause intermixed output from
both processes.
Use the wait() function so that the processes do not access the file
at the same time or open unique file descriptors. Some like stdout or stderr
will be shared unless synchronized using wait() or some other mechanism.
The file close on exit is another gotcha. A terminating process will close
files before exiting.
File locks set by the parent process are not inherited by the child process.
[Potential Pitfall]: Race conditions can be
created due to the unpredictability of when the kernel scheduler runs
portions or time slices of the process. One can use wait().
the use of sleep() does not guarentee reliability of execution
on a heavily loaded system as the scheduler behavior is not predictable by the
application.
Note on exit() vs _exit():
The C library function exit() calls the kernel system call
_exit() internally.
The kernel system call _exit() will cause the kernel to close descriptors,
free memory, and perform the kernel terminating process clean-up.
The C library function exit() call will flush I/O buffers and
perform aditional clean-up before calling _exit() internally.
The function exit(status) causes the executable to return
"status" as the return code for main().
When exit(status) is called by a child process,
it allows the parent process to examine the terminating status of the child
(if it terminates first). Without this call (or a call from main()
to return())
and specifying the status argument, the process will not return a value.
The vfork() function is the same as fork() except that
it does not make a copy of the address space. The memory is shared
reducing the overhead of spawning a new process with a unique copy of all
the memory.
This is typically used
when using fork() to exec() a process and terminate.
The vfork() function also executes the child process first and
resumes the parent process when the child terminates.
#include <iostream> #include <string>
// Required by for routine #include <sys/types.h> #include <unistd.h>
using namespace std;
int globalVariable = 2;
main() { string sIdentifier; int iStackVariable = 20;
pid_t pID = vfork(); if (pID == 0) // child { // Code only executed by child process
sIdentifier = "Child Process: "; globalVariable++; iStackVariable++; cout << sIdentifier; cout << " Global variable: " << globalVariable; cout << " Stack variable: " << iStackVariable << endl; _exit(0); } else if (pID < 0) // failed to fork { cerr << "Failed to fork" << endl; exit(1); // Throw exception } else // parent { // Code only executed by parent process
Child Process: Global variable: 3 Stack variable: 21 Parent Process: Global variable: 3 Stack variable: 21
Note: The child process executed first, updated the variables which are shared
between the processes and NOT unique, and then the parent process
executes using variables which the child has updated.
[Potential Pitfall]: A deadlock condition may occur
if the child process does not terminate, the parent process will not proceed.
The function clone() creates a new child process which shares memory,
file descriptors and signal handlers with the parent.
It implements threads and thus launches a function as a child.
The child terminates when the parent terminates.
See the YoLinux POSIX threads tutorial
The parent process will often want to wait until all child processes have been completed. this can be implemented with the wait() function call.
wait(): Blocks calling process until the child process terminates.
If child process has already teminated, the wait() call returns immediately.
if the calling process has multiple child processes, the function returns when one returns.
waitpid(): Options available to block calling process for a particular child process not the first one.
Code snipet:
#include <sys/wait.h>
...
pid_t pID = set to child process id with call to fork OR:
// If set <-1, wait for any child process whose process group ID = abs(pID)
// If = -1, wait for any child process. Same as wait().
// If = 0, wait for any child process whose process group ID is same as calling process.
// If > 0, wait for the child whose process ID = pID.
...
int childExitStatus;
pid_t ws = waitpid( pID, &childExitStatus, WNOHANG);
if( WIFEXITED(childExitStatus) )
{
// Child process exited thus exec failed.
// LOG failure of exec in child process.
cout << "Result of waitpid: Child process exited thus exec failed." << endl;
}
OR
... int childExitStatus;
pid_t ws = waitpid( pID, &childExitStatus, 0);
if( !WIFEXITED(childExitStatus) ) { cerr << "waitpid() exited with an error: Status= " << WEXITSTATUS(childExitStatus) << endl; } else if( WIFSIGNALED(childExitStatus) ) { cerr << "waitpid() exited due to a signal: " << WTERMSIG(childExitStatus) << endl; }
Notes:
See man page for options: WNOHANG, WUNTRACED.
See man page for return macros: WIFEXITED(), WEXITSTATUS(), WIFSIGNALED(), WTERMSIG(), WIFSTOPPED(), WSTOPSIG().
See man page for errors: ECHILD, EINVAL, EINTR. (Also see sample of error processing below.)
Avoids orphaned process group when parent terminates.
When parent dies, this will be a zombie. (No parent process. Parent=1)
Instead, create a new process group for the child.
Later process the group is terminated to stop all spawned processes.
Thus all subsequent processes should be of this group if they are to be
terminated by the process group id.
Process group leader has the same process id and group process id.
If not changed then the process group is that of the parent. Set the process
group id to that of the child process.
This is the real reason to set up a process group. One may kill all the
processes in the process group without having to keep track of how many
processes have been forked and all of their process id's.
See /usr/include/bits/signum.h for list of signals.
...
int killReturn = killpg( pID, SIGKILL); // Kill child process group
if( killReturn == ESRCH) // pid does not exist { cout << "Group does not exist!" << endl; } else if( killReturn == EPERM) // No permission to send signal { cout << "No permission to send signal!" << endl; } else cout << "Signal sent. All Ok!" << endl;
The system() call will execute an OS shell command as described by a
character command string. This function is implemented using fork(),
exec() and waitpid().
The command string is executed by calling /bin/sh -c command-string.
During execution of the command, SIGCHLD will be blocked, and SIGINT and
SIGQUIT will be ignored.
The call "blocks" and waits for the task to be performed before continuing.
The statement "Command done!" will not print untill the "ls -l" command has completed.
The popen() call opens a process by creating a pipe, forking, and
invoking the shell (bourne shell on Linux). The advantage to using popen() is that it
will allow one to interrogate the results of the command issued.
This example opens a pipe which executes the shell command "ls -l".
The results are read and printed out.
The exec() family of functions will initiate a program from within a program. They are also various front-end functions to execve().
The functions return an integer error code. (0=Ok/-1=Fail).
execl() and execlp():
The function call "execl()" initiates a new program in the same environment in which it is operating.
An executable (with fully qualified path. i.e. /bin/ls) and arguments are passed to the function. Note that "arg0" is the command/file name to execute.
Where all function arguments are null terminated strings. The list of arguments is terminated by NULL.
The routine execlp() will perform the same purpose except that it will
use environment variable PATH to determine which executable to
process. Thus a fully qualified path name would not have to be used.
The first argument to the function could instead be "ls".
The function execlp() can also take the fully qualified name as it also resolves explicitly.
This is the same as execl() except that the arguments are passed as
null terminated array of pointers to char. The first element "argv[0]" is the command name.
The routine execvp() will perform the same purpose except that it will
use environment variable PATH to determine which executable to
process. Thus a fully qualified path name would not have to be used.
The first argument to the function could instead be "ls".
The function execvp() can also take the fully qualified name as it also resolves explicitly.
"UNIX Network Programming, Volume 1: Sockets Networking API" Third Edition
by W. Richard Stevens, Bill Fenner, Andrew M. Rudoff, Richard W. Stevens
ISBN # 0131411551, Addison-Wesley Pub Co; 3 edition (October 22, 2003)
This book covers POSIX, IPv6, network APIs, sockets
(elementary, advanced, routed, and raw), multicast, UDP, TCP, Threads,
Streams, ioctl. In depth coverage of topics.
"UNIX Network Programming, Volume 1: Networking APIs - Sockets and XTI" Second Edition
by W. Richard Stevens
ISBN # 013490012X, Prentice Hall PTR
This book covers network APIs, sockets + XTI,
multicast, UDP, TCP, ICMP, raw sockets, SNMP, MBONE. In depth coverage
of topics.
"UNIX Network Programming Volume 2: Interprocess Communications"
by W. Richard Stevens
ISBN # 0130810819, Prentice Hall PTR
This book covers semaphores, threads, record locking, memory mapped I/O,
message queues, RPC's, etc.
"Advanced Linux Programming"
by Mark Mitchell, Jeffrey Oldham, Alex Samuel, Jeffery Oldham
ISBN # 0735710430, New Riders
Good book for programmers who already know how to program and just need
to know the Linux specifics. Covers a variety of Linux tools, libraries,
API's and techniques. If you don't know how to program, start with a
book on C.
"Advanced UNIX Programming" Second Edition
by Marc J. Rochkind
ISBN # 0131411543, Addison-Wesley Professional Computing Series
"Advanced Programming in the UNIX Environment" First Edition
by W. Richard Stevens
ISBN # 0201563177, Addison-Wesley Professional Computing Series
It is the C programmers guide to programming on the UNIX platform.
This book is a must for any serious UNIX/Linux programmer. It covers all
of the essential UNIX/Linux API's and techniques.
This book starts where the basic C programming book leaves off.
Great example code.
This book travels with me to every job I go to.
"Advanced Unix Programming"
by Warren W. Gay
ISBN # 067231990X, Sams White Book Series
This book covers all topics in general: files,
directories, date/time, libraries, pipes, IPC, semaphores, shared
memory, forked processes and I/O scheduling. The coverage is not as in
depth as the previous two books (Stevens Vol 1 and 2)
"Linux Programming Bible"
by John Goerzen
ISBN # 0764546570, Hungry Minds, Inc
This covers the next step after "C" programming 101.
Dr. Dobb's Journal
Free subscription to the premier resource for
professional programmers and software developers. Multi-language and
multi-platform with program listings, coding tips, design issue
discussions and algorithms. Subscribe here!