The signal
API registers the provided function pointer to be called upon receipt of the specified signal. It is possible to register the same function pointer against multiple signals, so the function when called will be provided the signal value that was received.
In Linux (and all operating systems that I am aware of) signal delivery is asynchronous to the process's execution, and the notification is immediate. The OS behaves as if it preempts the program and injects the signal handler function call on top of whatever the program is currently doing. The OS is aware which signal was delivered, and passes it as the parameter to the function call.
Note: It is possible to use raise
to generate a synchronous signal to the program. But, it will typically use the OS service to deliver it to the process rather than directly invoke the signal handler.
If you use a debugger and set a breakpoint within your signal handler, and deliver the appropriate signal, you might see that the backtrace will show that the signal handler is being injected by the OS.
For example, consider the program:
void signalhandler (int sig)
{
write(2, "signal!\n", 8);
}
void foo (void)
{
for (;;) {}
}
int main (void)
{
signal(SIGINT, signalhandler);
foo();
}
When running the program in gdb
, you can deliver the signal with the signal
command. The resulting backtrace would look like:
(gdb) signal SIGINT
Continuing with signal SIGINT.
Breakpoint 1, signalhandler (sig=2) at s.c:5
5 write(2, "signal!\n", 8);
(gdb) bt
#0 signalhandler (sig=2) at s.c:5
#1 <signal handler called>
#2 foo () at s.c:10
#3 0x08048498 in main () at s.c:16
Note: It is important to realize that the signal handler function call should not be treated like a regular function call. Since the OS injects the call, it has limitations that are discussed below.
The signal call can be injected at some arbitrary place in your code execution. For this reason, POSIX mandates certain functions to be safe to be called from signal handlers. Functions that are not designed to be re-entrant run the risk of being in an inconsistent state if it is interrupted by a injected call to the signal handler function, which then in turn calls the interrupted function.
As an example of a problem that could happen, suppose you are writing code that is manipulating a data structure, such as removing a node from a linked list. However, if the pointers of the elements have not been completely fixed up when the signal is delivered, then the signal handler may see a corrupted linked list. This situation can happen more often than you might assume, especially if you call a function that requires heap allocation.
Thus, it is often safest to make the signal handler dead simple. For example, the signal handler may simply set a flag, and your application code would then need code to detect whether or not the flag was set.