CMPS 3600 Lab-3 - Signals, Signal Masks, and Signal Handling

This lab introduces

resources:
A solution to this lab will likely include the following functions:

   a handler   - your own function definition.
   sigfillset  - initialize and fill a signal set
   sigprocmask - examine and change blocked signals (here is where you block all signals)
   sigaction   - examine and change a signal action (register a handler, required)
   sigdelset   - delete a signal from a signal set
   sigsuspend  - wait for a signal
   kill        - send a signal to a process
   wait        - wait for a child process to end
   open        - open a file
   write       - write to a file
   close       - close a file

The general idea is this...
   1. declare a mask
   2. fill the mask
   3. block every signal in the mask
   4. remove one signal from the blocked mask

Lab-3 introduces the use of signals, signal masks and signal handling by using kill(2) to send signals from parent to child. You will be introduced to the challenges involved in synchronizing multiple processes. Refer to the reference links above for detailed help. There are several signal handling intefaces available under Linux (BSD and ANSI-C/POSIX). We will use the POSIX (Portable Operating System Interface) signal handling interface (which is a newer one) although you may see some code written in older interfaces.

You will communicate between a parent and its child through signals delivered by the kill system call. Delivering a signal is done in kernel mode. The kernel determines whether one process has permission to send a signal to another process. Kill is also a shell command; i.e., from the shell a user can send signals SIGTOP, SIGCONT and SIGTERM to a background process. For example:

           $ ./a.out &
           Child PID is 32360
           [1] 32359
           $ kill -STOP 32360
           stopped by signal 19
           $ kill -CONT 32360
           continued
           $ kill -TERM 32360
           killed by signal 15
           [1]+  Done  ./a.out
           $
Before starting to code understand the concepts covered in the example files for this week. Copy all files including the Makefile into your 3600/3 directory. Then compile all source files:
        $ cd
        $ cd 3600/3
        $ cp /home/fac/gordon/public_html/3600/examples/3/* .  
        $ make
Follow the instructions in the header of each file to execute and test. Read the code and the comments in each file.

LAB INSTRUCTIONS.
Your job is to write a program mylab3.c that forks one child. The child opens log and writes "Go CSUB" to the file. The child then goes into a ~~ deep sleep ~~ with sigsuspend(2). Sigsuspend is a system call that waits until a signal wakes it up. While in sigsuspend block all signals except SIGUSR1. You can achieve this because the mask that you pass to sigsuspend goes into effect at the time of the call and then the original mask goes back into effect as soon as sigsuspend returns. The mask that you pass needs to be full except for SIGUSR1. This ensures that the only signal that will wake the child up is SIGUSR1.

Note:
The SIGUSR1 and SIGUSR2 signals are set aside for you to use any way you want.
triggering SIGUSR1 & 2

Next, code a SIGUSR1 signal handler for the child. In the signal handler the child writes " (got the signal) " to its log and returns; it does not call exit(). The child writes "Roadrunners!", closes its log file and exits with status code 0. Here is a snippet of the child's code:

   write(logfd, "Go CSUB", 7);        # line 1 
   sigsuspend(&mask);                 # line 2
   write(logfd, "Roadrunners!", 12);  # line 3
   close(logfd);                      # line 4
   exit(0);                           # line 5

Meanwhile,
after the fork the parent is going to immediately send two signals to its child: SIGTERM followed by SIGUSR1, call wait(2), harvest the child's code, display the code on the screen and exit. There are a few things that can go very wrong in this simple scenario.
First, since the child does not have a handler for SIGTERM the child will immediately terminate unless the child's process blocks this signal.
Second, the child will also terminate prematurely if the parent sends SIGUSR1 to its child before the child has a chance to setup its SIGUSR1 handler, open its log file and go into sigsuspend. In the snippet of the child's code above, these scenarios occur if either signal is delivered before line #2. In either case the child will immediately terminate since by default SIGUSR1 and SIGTERM terminate the process.

It is a race condition as to which line of code in parent and child executes first. The solution to this problem is to protect the parent and the child by blocking all signals before the fork. Since the child inherits its parent's process mask the child is immediately protected.

You should also setup the SIGUSR1 handler before the fork. The child will have a handler ready to activate.

To ensure that SIGTERM does not sneak in while the handler is being executed, block all signals in the handler. If you do all this you are safe.

But there is another potential problem to be solved...
Let's say the child's process mask is immediately blocking all signals. Right after the fork the parent sends the two signals and then goes into wait(). If the child does not change its process mask to allow SIGUSR1 in before it goes into sigsuspend, the parent and child will be deadlocked; i.e., the child is waiting on a signal from the parent that it will never receive because it is blocked and the parent is waiting on a signal from its child that it will never receive because the child is waiting too.....

To prevent deadlock you must pass a process mask to sigsuspend that opens up SIGUSR1 only (if you open up everything then SIGTERM will sneak in and the child will terminate prematurely).

If all is working properly, you should be able to run your code through strace and see the process mask change, the signal handler setup, the two signals from parent to child and the SIGUSR1 signal received from the child after the sigsuspend call. Output from program runs:

 $ ./lab3
 child terminated with code 0 

 $ cat log
 Go CSUB (got the signal) Roadrunners!

Sample run using strace...
 $ strace -e trace=signal -f ./lab3  2> err    # -f means trace child
 child terminated with code 0 
 $ cat err

 rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], NULL, 8) = 0
 rt_sigaction(SIGUSR1, {0x400ba5, ~[RTMIN RT_1],
     SA_RESTORER|SA_RESTART, 0x7fdbcbbd21e0}, NULL, 8) = 0
 Process 8100 attached
 [pid  8099] kill(8100, SIGTERM)         = 0
 [pid  8099] kill(8100, SIGUSR1)         = 0
 Process 8099 suspended
 [pid  8100] rt_sigsuspend(~[USR1 RTMIN RT_1]) = ?
     ERESTARTNOHAND (To be restarted)
 [pid  8100] --- SIGUSR1 (User defined signal 1) @ 0 (0) ---
 [pid  8100] rt_sigreturn(0x3)           = -1 EINTR (Interrupted system call)
 Process 8099 resumed
 Process 8100 detached

 $ cat log
 Go CSUB (got the signal) Roadrunners!
 

Note. In the above output two realtime signals are not blocked (RTMIN and RT_1). To manipulate realtime signals you need different system calls: Linux 2.2 and later: rt_sigaction(2), rt_sigpending(2), rt_sigprocmask(2), rt_sigreturn(2), rt_sigsuspend(2), rt_sigtimedwait(2). Don't be concerned about realtime signals for this lab.

What to turn in...

At 9:50am I will get a snapshot of your files

3600/3/mylab3.c
3600/3/Makefile