We will investigate handling of signals. Signals are similar in effect to hardware interrupts, but can be programmed and controlled without entering supervisor mode. Signals have their associated constants such as SIGINT, which hold the signal number. Every type of signal has its own number, SIGALRM for timer, SIGINT for interruption etc.
Sending signals from keyboard
You can send a signal to a process with kill() system call. For sending a signal from command-line you can use the 'kill' utility with associated process number and signal number. While interesting, it is inconvenient for this practice.
You can send some signals to running program using only your keyboard:
- Ctrl-C sends an INT signal (SIGINT), which terminates the process
- Ctrl-\ sends a QUIT signal (SIGQUIT), which terminates the process (with coredump).
- Ctrl-Z sends a TSTP signal (SIGTSTP), which makes the program to stop and suspend execution. To get it running again, use 'fg' (foreground) and 'bg' (background) utilities from command-line.
- Ctrl-S sends TSTP signal, but does not give you the command line. To start the program again, press Ctrl+Q. This often happens when one is used to GUI editors where Ctrl-S is Save command. (And it seems that the command has been deprecated for newer Linux versions)
- Write a small program with an infinite loop, which prints a dot (.) for every 1/2 seconds (use usleep() and printf() for educational fun), add a newline to escape line-buffering. Check its reaction to CTRL+C
- Update the program to catch ^C and not to terminate with the signal. (Use CTRL+\ to quit (test whether it works; alternative would be to press ^Z, then kill with the process number that ^Z prints).
- Update the program to count ^C keypresses and print the number every time the key is pressed. Add a newline to avoid line-buffering issues.
- In a naïve interpretation (which I hope), you might end up with a situation in which the printf() from main-loop runs while the handler accesses printf(). This is an error for printf to run while running (this is called re-entrancy). Sadly, it's not feasible to manifest an error here; I wish we had a better method, but we'll need to believe in this hypothetical accident this time...
- Fix the error by blocking the signals while printf() runs. Use sigprocmask() function for masking (blocking) signals. You might find sigemptyset() useful. Remember to restore the original signal blocking behaviour afterwards. You are "protecting the critical section" by masking signals. Show the result here, or save a copy for next step
- For real-life programs it is not practical to block signals on every printf() call. Re-write the program so, that handler sets a global variable which holds 1 when signal has arrived, and 0 when it hasn't. Print the number on signal arrival, otherwise the dot. Remember to re-set the global variable. Note that there might still be a need to block signals when the signal arrives during the checking itself. Did you set the global variable as 'volatile'? Show the result
The use of signal() is deprecated in favour of sigaction(), but for our educational purposes the signal() function is slightly better. In real life take the effort to use sigaction().
Note that signals are even more compilcated, it is possible for another signal to arrive while you are handling a signal (and it will pre-empt your handler). This can be prevented by masking some (or all) signals for the duration of the handler.
It is customary to write handlers as short as possible for the exact same reason and check their arrival on a convenient occasion in your program itself.