CS 134

Quick Refresher: Signals in Unix-like Systems

  • Dog speaking

    When we talked about processes, we talked about signals waiting and signal masks. I sort of rememeber something about that from CS 105, but it's a bit fuzzy. Can you refresh my memory?

  • PinkRobot speaking

    Okay…

What are Signals?

Signals are a form of interprocess communication used in Unix-like operating systems. They're essentially software interrupts sent to a program to notify it of a significant event.

  • Hedgehog speaking

    So, like, it's the OS poking the program and saying, “Hey, something happened!”?

  • PinkRobot speaking

    Exactly! It's a way for the OS (or other processes) to communicate with a running program.

Common Signals

Here are some signals you might encounter frequently:

Signal Number Description
SIGINT 2 Interrupt from keyboard (usually Ctrl+C)
SIGKILL 9 Force process to stop (can't be ignored)
SIGTERM 15 Termination signal (can be handled)
SIGSEGV 11 Segmentation fault
SIGALRM 14 Alarm clock (from alarm() system call)
  • Horse speaking

    Hay! What's with the numbers?

  • PinkRobot speaking

    All the signals are numbered, as that's what the kernel really uses internally. In C, you can refer to signals either by name (like SIGINT) or by number (like 2). But actually, although some numbers are standardized, the numbers can vary between systems. So it's best to use the names.

  • Cat speaking

    And what's the difference between SIGKILL and SIGTERM?

  • PinkRobot speaking

    Good question! SIGKILL forces the process to stop immediately, while SIGTERM asks it to terminate gracefully. Programs can't catch or ignore SIGKILL, but they can handle SIGTERM.

Signal Handlers

Programs can define custom behaviors for most signals using signal handlers. Here's a simple example:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void
sigint_handler(int sig)
{
        printf("Ouch! That tickles!\n");
}

int
main()
{
        signal(SIGINT, sigint_handler);
        printf("Try pressing Ctrl+C to stop this program!\n");
        for (int i = 0; i < 10; i++) {
                printf("You have %d second%s to wait...\n", 10 - i,
                       i == 9 ? "" : "s");
                sleep(1);
        }
        return 0;
}
  • Cat speaking

    So this program will print, “Ouch! That tickles!” instead of quitting when I press Ctrl-C?

  • PinkRobot speaking

    Exactly! Try running it and see what happens.

Signal Masks

A signal mask is a set of signals whose delivery is currently blocked for a process. When a signal is blocked, it won't be delivered until it's unblocked. Try adding the following code to the previous example at the beginning of main():

    sigset_t set;                           // Define a set of signals
    sigemptyset(&set);                      // Clear the set
    sigaddset(&set, SIGINT);                // Add SIGINT to the set
    sigprocmask(SIG_BLOCK, &set, NULL);     // Block SIGINT

When you run the program, you'll notice that pressing Ctrl-C no longer has any effect. The signal is blocked!

  • Hedgehog speaking

    Why would we want to block signals?

  • PinkRobot speaking

    Good question! Sometimes we need to perform a critical operation without interruption. Blocking signals helps ensure that state.

We can also unblock signals using SIG_UNBLOCK or set the mask to a specific set of signals using SIG_SETMASK.

Besides no longer printing, “Ouch! That tickles!”, what other effect does blocking the SIGINT signal have on the program compared to what we saw before? Hint: Try pressing Ctrl+C quite rapidly in both versions of the program.

  • Dog speaking

    I saw a difference! With the one that didn't block the signal, the program exited faster when I pressed Ctrl-C repeatedly. But with the blocked signal, it always waited the full 10 seconds. Why is that?

  • PinkRobot speaking

    Good question…

Signals and System Calls

When a signal arrives while a process is in the middle of a system call, the OS has a choice to make:

  1. Interrupt the system call immediately.
  2. Allow the system call to complete before delivering the signal.

Traditionally, Unix-like systems have used the first approach, interrupting the system call. This choice was part of Unix's “Worse is Better” philosophy, favoring simplicity of implementation. Many system calls can return EINTR (interrupted system call) to indicate that a signal interrupted the call.

The more full-featured signals API includes a function sigaction() to control this behavior. This function allows you to specify whether a system call that was interrupted by a signal should restart or not (by passing a SA_RESTART flag). But alas, even there, not all system calls are automatically restarted. In particular, sleep() is not automatically restarted; instead it returns early with the amount of time remaining.

  • Horse speaking

    Hay! So it's not actually to interrupt or not, it's whether to restart the system call or not?

  • PinkRobot speaking

    That's right! That's the design decision that Unix made.

  • Rabbit speaking

    Mostly… In practice, often some part of a system call is actually uninterruptible (e.g., while it's trying to atomically update critical kernel state), but usually that part is very short. Usually…

Wrapping Up

Signals are a powerful feature in Unix-like systems, but they can also be tricky to use correctly. Remember:

  • Signals provide a way for the OS or other processes to communicate with a running program.
  • Most signals can be caught and handled, but some (like SIGKILL) cannot.
  • Signal masks allow you to temporarily block signal delivery.
  • The interaction between signals and system calls can be complex and system-dependent.

Now, back to our regularly scheduled programming about processes and threads!

(When logged in, completion status appears here.)