Creating Child Processes with fork and Replacing Them Using exec
The project directory structure contains two source files and a Makefile for building the executables.
Makefile:
all: controller worker
controller: controller.cpp
g++ controller.cpp -o controller
worker: worker.cpp
g++ worker.cpp -o worker
clean:
rm -f controller worker
controller.cpp (Master Process):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#define DELAY_MS(ms) usleep((ms) * 1000)
int main(int argc, char* argv[]) {
printf("Controller PID: %d, Arguments: ", getpid());
for (int idx = 0; idx < argc; idx++) {
printf("[%d]=%s ", idx, argv[idx]);
}
printf("\n");
struct sigaction child_signal_action;
child_signal_action.sa_handler = SIG_DFL;
child_signal_action.sa_flags = SA_NOCLDWAIT;
sigaction(SIGCHLD, &child_signal_action, NULL);
pid_t child_pid = fork();
if (child_pid == 0) {
int exec_result = execl("./worker", "param1=value1", "param2=value2", "param3=98765", NULL);
if (exec_result == -1) {
perror("execl failed");
return EXIT_FAILURE;
}
printf("This line should not be printed after successful exec.\n");
return 0;
}
for (int counter = 0; counter < 10; counter++) {
DELAY_MS(2000);
printf("Controller iteration: %d\n", counter);
}
printf("Controller process completed.\n");
return 0;
}
worker.cpp (Slave Process):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define DELAY_MS(ms) usleep((ms) * 1000)
int main(int argc, char* argv[]) {
printf("Worker PID: %d, Arguments: ", getpid());
for (int idx = 0; idx < argc; idx++) {
printf("[%d]=%s ", idx, argv[idx]);
}
printf("\n");
for (int loop_counter = 0; loop_counter < 40; loop_counter++) {
DELAY_MS(800);
printf("Worker output: ---------------------------------------- count=%d\n", loop_counter);
}
printf("Worker process completed.\n");
return 0;
}
Compile and run the program:
$ make
$ ./controller
Sample output:
Controller PID: 8921, Arguments: [0]=./controller
Worker PID: 8922, Arguments: [0]=param1=value1 [1]=param2=value2 [2]=param3=98765
Worker output: ---------------------------------------- count=0
Worker output: ---------------------------------------- count=1
Controller iteration: 0
Worker output: ---------------------------------------- count=2
Worker output: ---------------------------------------- count=3
Controller iteration: 1
Worker output: ---------------------------------------- count=4
Worker output: ---------------------------------------- count=5
Worker output: ---------------------------------------- count=6
Controller iteration: 2
...
Controller iteration: 9
Controller process completed.
Worker output: ---------------------------------------- count=24
...
Worker output: ---------------------------------------- count=39
Worker process completed.
Key observations:
- The controller process (PID 8921) exits after its loop completes.
- The worker process (PID 8922) continues running and exits independent.
- When using
execlto launch the worker, the first argument passed (param1=value1) becomesargv[0]in the worker'smainfunction, replacing the typical program file path.
Process relationship can be verified:
ps -A | grep -E "controller|worker"
Understanding fork:
- When the parent process executes
fork(), it creates a duplicate child process. - Both processes initially share identical memory content.
- The child process inherits open file dsecriptors and other kernel objects from the parent.
- However, after
execis called in the child, its memory image is complete replaced by the new program, while inherited resources like file descriptors may remain open unless explicitly closed.
For example, if the parent opens a file and the child closes it, subsequent write operations by the parent may not produce errors but might not write data correctly to the file.