I'm working through example in Advanced Programming in the UNIX Environment:
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
static void
sig_hup(int signo)
{
printf("SIGHUP received, pid = %ld\n", (long)getpid());
}
static void
pr_ids(char *name)
{
printf("%s: pid = %ld, ppid = %ld, pgrp = %ld, tpgrp = %ld\n",
name, (long)getpid(), (long)getppid(), (long)getpgrp(),
(long)tcgetpgrp(STDIN_FILENO));
fflush(stdout);
}
int
main(void)
{
char c;
pid_t pid;
pr_ids("parent");
if ((pid = fork()) < 0) {
exit(1);
} else if (pid > 0) { /* parent */
sleep(5); /* sleep to let child stop itself */
} else { /* child */
pr_ids("child");
signal(SIGHUP, sig_hup); /* establish signal handler */
printf("test\n");
kill(getpid(), SIGTSTP); /* stop ourself */
pr_ids("child"); /* prints only if we're continued */
if (read(STDIN_FILENO, &c, 1) != 1)
printf("read error %d on controlling TTY\n", errno);
}
exit(0);
}
The idea is that:
When running this program from shell, I get flaky results. The example in book show this output:
$ ./a.out
parent: pid = 6099, ppid = 2837, pgrp = 6099, tpgrp = 6099
child: pid = 6100, ppid = 6099, pgrp = 6099, tpgrp = 6099
$ SIGHUP received, pid = 6100
child: pid = 6100, ppid = 1, pgrp = 6099, tpgrp = 2837
read error 5 on controlling TTY
However, my results are:
$ ./a.out
parent: pid = 294729, ppid = 291330, pgrp = 294729, tpgrp = 294729
child: pid = 294730, ppid = 294729, pgrp = 294729, tpgrp = 294729
$ ./a.out
parent: pid = 295709, ppid = 291330, pgrp = 295709, tpgrp = 295709
child: pid = 295710, ppid = 295709, pgrp = 295709, tpgrp = 295709
SIGHUP received, pid = 295710
$ ./a.out
parent: pid = 294722, ppid = 291330, pgrp = 294722, tpgrp = 294722
child: pid = 294723, ppid = 294722, pgrp = 294722, tpgrp = 294722
SIGHUP received, pid = 294723
child: pid = 294723, ppid = 2065, pgrp = 294722, tpgrp = 294722
I'd like to get an idea on why there is such variance between the results.
I think your issue is happening due to a race condition a process terminating and the signal's delivery in the kernel. Whenever the process is terminated, all signal processing is stopped (except for SIGCONT and SIGKILL). So the SIGHUP is handled when a process is resumed, which can only happen after SIGCONT is received and handled. Take a look at this other stackoverflow answer.
I think you need to add the following call to waitpid() within else if (pid > 0) check
int status;
waitpid(pid, &status, WUNTRACED);
// parent waits for child to actually stop before moving forward
if (WIFSTOPPED(status)) {
fflush(stdout);
}
In your else block you should add a call to fflush(stdout) after signal(SIGHUP, sig_hup) and again after your call to read() , so something like this:
else { /* child */
pr_ids("child");
signal(SIGHUP, sig_hup); /* establish signal handler */
fflush(stdout); /* flush everything before stopping */
printf("test\n");
kill(getpid(), SIGTSTP); /* stop ourself */
pr_ids("child"); /* prints only if we're continued */
if (read(STDIN_FILENO, &c, 1) != 1)
printf("read error %d on controlling TTY\n", errno);
fflush(stdout);
}
In your sig_hup() function, you should also add fflush(stdout) like this:
static void
sig_hup(int signo)
{
printf("SIGHUP received, pid = %ld\n", (long)getpid());
fflush(stdout); // flush immediately
}
The calls to fflush() make sure the output is visible immediately (not buffered), thus hopefully leading to more consistent output.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With