Skip to Content
Course content

123: Raising and Catching Signals

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're working in a large office building. You're deep in a flow state, typing away at your keyboard, totally focused on a complex problem. Suddenly, the fire alarm screams. You don't finish the sentence you're writing; you don't carefully save your file and shut down your computer. You stop immediately, stand up, and follow the evacuation plan you were taught during orientation. Once you're safely outside and the fire marshal says it was just a drill, you might head back to your desk and try to pick up where you left off.

In C, a signal is exactly like that fire alarm. It's an asynchronous notification sent to your process to tell it that something happened—usually something urgent—that requires immediate attention. The "evacuation plan" is what we call a signal handler.

Mapping the Alarm to the Code

Let's break down how that office analogy translates to the actual mechanics of the C language:

  • The Alarm Trigger: This is the signal being "raised." It could be the OS telling your program it's time to die (SIGKILL), the user hitting Ctrl+C (SIGINT), or your program accidentally trying to read memory it doesn't own (SIGSEGV).
  • The Orientation/Training: This is when you use the signal() function to tell the OS, "If you ever send me signal X, I want you to run function Y."
  • The Evacuation: This is the execution of your handler function. The CPU literally pauses your main() function right in its tracks, jumps to your handler, and runs it.
  • Returning to the Desk: After the handler finishes, the program attempts to resume exactly where it was interrupted.

Teaching Your Program How to React

Most people are used to their programs just "crashing" when they hit Ctrl+C. But if you're writing a database or a server, you can't just vanish; you need to close file handles and save state. Here is how I usually set up a basic catch for SIGINT.

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

void handle_sigint(int sig) {
    printf("\nCaught signal %d! Cleaning up temporary files before exiting...\n", sig);
    // In a real app, you'd delete temp files or close DB connections here
    exit(0);
}

int main() {
    // This is the "orientation" phase. 
    // We map SIGINT (Ctrl+C) to our handler function.
    signal(SIGINT, handle_sigint);

    printf("I'm running. Try hitting Ctrl+C to stop me!\n");

    while(1) {
        printf("Working hard...\n");
        sleep(1); 
    }
    return 0;
}

Triggering the Alarm Manually

You don't always have to wait for the user or the OS to send a signal. Sometimes you want your program to signal itself, or one process to signal another. We use the raise() function for the former and kill() for the latter. Don't let the name kill() fool you; it's not just for terminating processes—it's just the generic name for "send a signal."

For example, if your program detects a corrupted configuration file that makes it impossible to continue, you might call raise(SIGABRT); to trigger an abnormal termination and generate a core dump for debugging.

The Danger of Doing Too Much in a Handler

Here is a bit of hard-earned advice: keep your handlers lean. Because a signal can hit at any microsecond, it might interrupt your program while it's in the middle of updating a global variable or holding a lock. If your handler also tries to update that same variable, you've just created a race condition that is a nightmare to debug.

In the professional world, we talk about "async-signal-safe" functions. Strictly speaking, printf() isn't actually safe to use inside a signal handler because it uses internal locks. For a simple learning project, it's fine, but in a production system, I'd simply set a volatile sig_atomic_t flag to 1 and let the main() loop check that flag and exit gracefully on its own.




📋 Practical Task

Build a Graceful Shutdown State-Saver

Your task is to create a program that simulates a long-running data processor. The program should maintain a counter that increments every second. However, instead of losing the current count when the user presses Ctrl+C, the program must catch the SIGINT signal and write the final counter value to a file named shutdown_state.txt before exiting.

Requirements:

  • Implement a signal handler for SIGINT.
  • The handler must open shutdown_state.txt, write the current value of the counter, and then terminate the program.
  • The counter must be declared in a way that the signal handler can access it (think about the volatile keyword).
  • The main() loop should print the current count every second to show the program is active.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.