CSE 422S: Studio 20

Linux Signal Handling


"[S]ignals are significantly more powerful and expressive when SA_SIGINFO-style handlers are used."

—Robert Love, Linux System Programming, 2nd Edition, Chapter 10, pp. 362

Signals are a simple yet powerful way for Linux processes to communicate in pre-defined ways. They are reasonably well suited for inter-process event handling during system operation, especially for parent-child process interactions. Signals can be sent to any process in the system, and can be recieved by defining special functions called signal handlers, which are executed upon receipt of the signal.

In this studio, you will:

  1. Generate and receive program signals
  2. Create custom signal handlers
  3. Observe signal safety guidelines
  4. Send and queue real-time signals with data

Please complete the required exercises below, as well as any optional enrichment exercises that you wish to complete. We encourage you to please work in groups of 2 or 3 people on each studio (and the groups are allowed to change from studio to studio) though if you would prefer to complete any studio by yourself that is allowed.

As you work through these exercises, please record your answers, and when finished upload them along with the relevant source code to the appropriate spot on Canvas. If you work in a group with other people, only one of you should please upload the answers (and any other files requested) for the studio, and if you need to resubmit (e.g., if a studio were to be marked incomplete when we grade it) the same person who submitted the studio originally should please do that.

Make sure that the name of each person who worked on these exercises is listed in the first answer, and make sure you number each of your responses so it is easy to match your responses with each exercise.


Required Exercises

  1. As the answer to the first exercise, list the names of the people who worked together on this studio.
  2. The following exercises are intended to acquaint you with signal delivery in Linux. On your Raspberry Pi 3, or on the shell.cec.wustl.edu server, download the program print500000.c. And easy way to download it is to issue the command:

    wget https://classes.engineering.wustl.edu/cse422/studios/print500000.c

    This is a very simple program that prints the numbers from 1 to 50000 in order.

    Build and run it. While it runs, try to interrupt it by pressing CTRL+C. Only press CTRL+C once.

    As the answer to this exercise, please report what you observed. How did the program behave at the moment you pressed CTRL+C? Please copy the last few lines of program output as part of your answer.

  3. There are many signals that can be sent or recieved in Linux. An overview of the signal mechanism can be found at the manual page displayed when you issue the terminal window command man 7 signal. Some commonly used signals are SIGINT (keyboard interrupt), SIGQUIT (telling a program to quit from keyboard), and SIGTERM (terminate process). Every signal in the system is associated with a default action (which typically terminates the recieving process), but each of these can be overridden simply by defining your own signal handling function, which is what we will now do.

    The modern, portable way to define a signal handler is to use the function sigaction(), which is documented by man 2 sigaction. Use this function to define a new handler for SIGINT within the print500000.c file. You will need to:

    In the body of your signal handler, add the following code:

    called[i] = 1;

    As the answer to this exercise, please describe the purpose of the called array.

  4. Build and run your program, and while it is running send it SIGINT a few times by hitting CTRL+C.

    As the answer to this exercise, copy and paste the last several lines of program output.

    NOTE: Overriding the SIGINT handler may make it difficult to terminate your program. You can alternately use CTRL+\ to send the SIGQUIT signal or the kill program to terminate your processes.

  5. Now, declare a variable of type volatile sig_atomic_t that acts as a flag to indicate that the program should terminate. Initialize the variable to 0.

    Modify your signal handler to also set this variable to 1.

    In the main function, inside the loop that prints the numbers from 1 to 50000, add a statement that checks the value of the flag. If it has been set to 1, your program should break out of the loop.

    Build and run it. Interrupt it with SIGINT, this time only pressing CTRL+C once.

    As the answer to this exercise, please report what you observed. How did the program behave at the moment you pressed CTRL+C? Please copy the last few lines of program output as part of your answer.

  6. One way that signal handlers differ from other functions of a user-space program is that they execute asynchronously with respect to the rest of a program. When called, they may interrupt the normal execution of the program at virtually any point. Consider what might happen, for example, if the same function is called from both the regular program flow as well as a signal handler. This could result in the function being called concurrently from two different contexts, even within a single-threaded program! This could cause data corruption or even deadlock if not handled appropriately.

    Even library functions are not immune to problems with such asynchrony.

    Additionally, add the following line of code to the handler function:

    printf("Caught SIGINT\n");

    Additionally, in your signal handler, comment out the statement that updates the flag to 1. This will once again allow your program to receive several SIGINT signals.

    Build your program, but this time run it using the following command:

    ./print | tee -i print500000.txt

    The tee utility allows a program's output to be sent both to standard output and to a specified file. The utility is documented in man 1 tee

    While your program is running, send it SIGINT a few times by hitting CTRL+C.

    Open the output file print500000.txt in a text editor that allows you to search for the signal handler's output, "Caught SIGINT." Observe where this was printed, and what numbers were printed before and after it.

    As the answer to this exercise, please describe what you see, and why you think you're observing this behavior. Please copy a few relevant lines of program output as part of your answer.

  7. For the reasons outlined above, it may not be safe to make many common system calls or library function calls from within a signal handler. Many functions, such as printf() are not guaranteed to execute correctly when called from within a handler, since they may be called concurrently from different contexts. Fortunately, there is a list of functions that is guaranteed to be safe when called from a handler. These functions are given in the signal safety documentation at man 7 signal-safety.

    Update your signal handler so that instead of the printf() function, it uses the write() function documented in the man 2 write page, to produce output from within the signal handler. In particular, you must:

    Repeat the previous run, interrupting the program several times, and again inspect the print500000.txt output file. As the answer to this exercise, explain briefly the differences you observed when using the write() function instead of printf().

  8. The next few exercises will explore the differences between standard signals and real-time signals.

    First, download the program receiver.c and build it.

    Look at the code, and as the answer to this exercise, briefly explain what it does.

  9. Now, write and compile a second program, sender.c, that takes a PID and a number of signals to send as arguments. Your program should send SIGUSR1 to the specified PID a number of times equal to the argument provided. It should then send SIGUSR2 once.

    Now, run your receiver program, and note its PID.

    Run your sender program in a new terminal window, providing the receiver's PID and a number of signals to send.

    As the answer to this exercise, please report how many times the receiver received SIGUSR, and why you think this behavior makes sense.

  10. Now, you will modify both programs to use real-time signals.

    In receiver.c, replace SIGUSR1 and SIGUSR2 with SIGRTMIN and SIGRTMIN+1 respectively.

    In your sender.c program, do the same thing, and replace calls to kill() with appropriate calls to sigqueue().

    The Linux kernel enforces resource limits to prevent userspace programs from hogging computational resources. One such limit is RLIMIT_SIGPENDING, which limits the number of queued real-time signals a process may have pending.

    In your sender.c program, use a call to getrlimit to determine the current limit (also called the soft limit) of this value, and ensure that the provided argument for the number of signals to send does not exceed this value. More information on getrlimit can be found with the following command:

    man 2 getrlimit

    Now, run your receiver and sender again, like in the previous exercise. As the answer to this exercise, please report how many times the receiver received SIGRTMIN, and tell us the current limit on the number of pending real-time signals.

  11. Now, we are going to explore sending signals with data. Instead of using two signals (one to increase a counter, and one to end the program), we will use a single signal type, but with accompanying data to indicate what action should occur.

    First, modify your sender.c program. The sigqueue function includes a union sigval parameter.

    This union contains an integer value, sival_int

    Where the sender would have previously sent SIGRTMIN, have it again send SIGRTMIN but with sival_int = 0.

    Where the sender would have previously sent SIGRTMIN + 1, have it now send SIGRTMIN but with sival_int = 1.

    Now, modify receiver.c so it only handles SIGRTMIN.

    To allow receiver.c to access the integer it has been sent, instead of setting ss.sa_handler, you will need to set ss.sa_sigaction. You will also need to add (using bitwise OR) the SA_SIGINFO flag to ss.sa_flags.

    Modify the signal handler function with a new signature:

    void sig_handler( int signum, siginfo_t * si, void * ucontext )

    You can access the sigval union through si->si_value

    Modify the signal handler appropriately so that it increments the count when sival_int == 0 and sets the done flag when sival_int == 1.

    Now, run your receiver and sender again, like in the previous exercise. As the answer to this exercise, please report how many times the receiver received SIGRTMIN with sival_int == 0.

  12. Things to Turn In:

    For this studio, please turn in the following: