Implement and use low-level UNIX
system calls such as getpid(),
fork(), kill() and
pause(). Students should consult
appropriate man pages (e.g. man 2 fork).
fork().
Using fork(), a process creates a child process that runs concurrently.
Students learn how the child inherits the parents environment, and how
the parent and child can interact, synchronize, and terminate cleanly.
Signals are a basic asynchronous IPC mechanism. Exchanging
SIGHUP signals between a parent
and child process demonstrates the processes can send and receive messages.
Threads are can be run or wait. The scheduler time-slices threads so they get cycles on a hardware thread. Another example is how a signal is delivered to a process. The scheduler either interrupts or uses the normal thread pre-emption and schedules the signal handler code for the thread's next time-slice. The handler exits and then the scheduler resumes scheduling the main thread.
As part of this project, and graded, is that you have been provided with a Makefile with three targets. Two of the three are not complete, and you must complete them.
Write a C program that creates a child process with fork(). The parent and
child alternately send each other the SIGHUP signal. Each time a process
receives SIGHUP, it decrements a counter and prints out the value. The counter
is initialized to a pre-defined MAX_COUNT. When the counter is decremented
to zero, program prints out BOOM!. Both processes are expected
to terminate cleanly.
kill. Other signals are
the SIGINT
signal which is sent by a Control-C, and the SIGSEGV, the
"segmentation fault" sent on illegal memory reference.
fork() gives the processes permission to signal each other.
Using fork() also gives easy access to the Process ID's (PID) that we will need,
as the child PID is returned to the parent by fork(), and the child can get the parent's
PID with getppid
wait system call.
pid and a signal sig,
a thread calling kill(pid,sid) sends a signal to the
referenced process. The signal is posted into a vector in the PCB
of the receiving process for delivery.
A function vector associates a signal handler with the signal.
The sig to use for this project is SIGHUP defined
in signal.h. The handler is installed into the function vector with,
sigaction(SIGHUP, struct sigaction *, NULL)and the signal is sent by,
kill(pid_t, SIGHUP)
When a thread for the process is readied to run the signal vector is consulted for any pending signals. If there is a pending signal, rather than return to where the thread left off, the signal handling code is run.
A process can wait for a signal by calling pause() (
see man 2 pause).
This call puts the thread in a wait state, waiting for a signal.
When the signal is received the thread is moved to the ready state.
The scheduler will run the signal handler on that thread
before continuing the code after the pause statement.
In this assignment, you will have to install a signal handler, but I (with ChatGPT) have written the signal handler for you. These things are tricky to get right. So the design principle was to make the handler as simple as possible. It simply sets a flag that the signal has been delivered.
SAMPLE RUN % ./signal-ping-pong P: started ... C: started ... P: 10 C: 10 P: 9 C: 9 P: 8 C: 8 P: 7 C: 7 P: 6 C: 6 P: 5 C: 5 P: 4 C: 4 P: 3 C: 3 P: 2 C: 2 P: 1 C: 1 P: 0 P: BOOM! C: 0 C: BOOM! %
pause to
wait for the signals. The hup_handler set got_hup_g
and exits. The main thread resets got_hup_g. The variable
makes sure the HUP code is run only if the wake up was because of a HUP signal.
got_hup_g, decrements then prints count; then
signals the child. It then pauses again.
got_hup_g, decrements then prints count.
It then signals the parent and pauses.
wait, to only exit when the child has exited. Else it is possible
that the HUP will be lost and the child would be stuck.

author: burton rosenberg
created: 8 sep 2025
update: 9 sep 2025