Quick Refresher: Signals in Unix-like Systems
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?
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.
So, like, it's the OS poking the program and saying, “Hey, something happened!”?
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) |
Hay! What's with the numbers?
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.
And what's the difference between
SIGKILLandSIGTERM?
Good question!
SIGKILLforces the process to stop immediately, whileSIGTERMasks it to terminate gracefully. Programs can't catch or ignoreSIGKILL, but they can handleSIGTERM.
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;
}
So this program will print, “Ouch! That tickles!” instead of quitting when I press Ctrl-C?
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!
Why would we want to block signals?
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.
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?
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:
- Interrupt the system call immediately.
- 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.
Hay! So it's not actually to interrupt or not, it's whether to restart the system call or not?
That's right! That's the design decision that Unix made.
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.)