Program fork_wait.c :
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
pid_t a = fork();
if (a != 0) {
int status;
printf("I am your father. (...) You know it to be true !");
wait(&status);
}
else {
printf("NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!");
sleep(2000);
}
return 0;
}
Bash :
$ cc fork_wait.c -o fork_wait && ./fork_wait
I am your father. (...) You know it to be true !
NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
Bash while execution :
$ pgrep fork_wait
37818
37819
$ kill -9 $(pgrep fork_wait | tail -1)
$ pgrep fork_wait
(nothing)
Who sends the SIGCHLD signal when the process is killed with SIGTERM ? Why is there no zombie process if I kill the son ?
Here's an adaptation of your first program. It uses the default SIGCHLD signal handling, which means that the signals are not delivered (by the kernel) to the parent process when a child dies (forkwait29.c
):
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t a = fork();
if (a != 0)
{
printf("%5d: I am your father. (...) You know it to be true!\n", getpid());
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
printf("PID %5d exited with status 0x%.4X\n", corpse, status);
}
else
{
printf("%5d: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!\n", getpid());
sleep(2000);
}
return 0;
}
An example run yields:
$ forkwait29
64001: I am your father. (...) You know it to be true!
64002: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
PID 64002 exited with status 0x0009
$
You could analyze the status returned (using WIFSIGNALED
, WIFTERMSIG
, WIFEXITED
, WEXITSTATUS
, WCOREDUMP
, etc) and it would show that the child died because it received signal 9, SIGKILL. As pointed out in the comments, your parent collected the dead child process (preventing it from becoming a (long-lasting) zombie) and exited.
You could add some signal handling like this (forkwait73.c
):
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
static volatile sig_atomic_t sig_caught = 0;
static void sig_handler(int signum)
{
sig_caught = signum;
}
int main(void)
{
pid_t a = fork();
if (a != 0)
{
printf("%5d: I am your father. (...) You know it to be true!\n", getpid());
struct sigaction sa = { 0 };
sa.sa_handler = sig_handler;
sa.sa_flags = SA_RESTART;
if (sigemptyset(&sa.sa_mask) != 0)
return 1;
sigaction(SIGCHLD, &sa, NULL);
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
{
printf("PID %5d exited with status 0x%.4X (caught = %d)\n",
corpse, status, sig_caught);
}
}
else
{
printf("%5d: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!\n", getpid());
sleep(2000);
}
return 0;
}
One sample run produced:
$ forkwait73
63964: I am your father. (...) You know it to be true!
63965: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
PID 63965 exited with status 0x000F (caught = 20)
$
When I omitted the SA_RESTART
flag on macOS Moneterey 12.2.1, I got a result like:
$ forkwait73
63929: I am your father. (...) You know it to be true!
63930: NOOOOOOOOOOOOOOOOOOOOO!!!! NOOOOOOO!!
$
Without the SA_RESTART, the parent process did not report the death of its child because the wait()
failed with errno == EINTR
. Instead of setting SA_RESTART, the loop could be revised to:
while ((corpse = wait(&status)) > 0 || errno == EINTR)
{
printf("PID %5d exited with status 0x%.4X (caught = %d)\n",
corpse, status, sig_caught);
errno = 0;
}
If you want to see a zombie process, you have to arrange for the parent not to wait()
for the child for a while. You could have it sleep too, and when the sleep()
call finishes, it could continue with the wait()
loop. You might arrange for it to handle another signal too so you can signal it to wake up and spot that it's child has died.