Let's say we have a program in C that uses the sleep() function
The program executes and goes to sleep. Then we type ctrl-c to send a SIGINT signal to the process.
We know that the default action upon receipt of a SIGINT is to terminate the process, we also know that the sleep() function resume the process whenever the sleeping process receives a signal.
And my textbook says in order to allow sleep() function to return, we must install a SIGINT handler like this:
void handler(int sig){
return; /* Catch the signal and return */
}
...
int main(int argc, char **argv) {
...
if (signal(SIGINT, handler) == SIG_ERR) /* Install SIGINT handler */
unix_error("signal error\n");
...
sleep(1000)
}
Althouth the code seems to be straightforward, I still have questions if I want to dig deeper:
Background: When the process is sleeping and we type ctrl-c to send SIGINT
Q1-My understanding is, Kernel sends SIGINT to the process by updating the SIGINT's corresponging pending bit in the pend bit vector, is my understanding correct?
Q2-The processor detects the existance of SIGINT, but since we overwrite the handler to make it return in stead of terminating the process, so our handler get executed, and then Kernel clears SIGINT's corresponging pending bit, is my understanding correct?
Q3- Since SIGINT's corresponging pending bit is cleared, then how can sleep() function gets return? I think it should be in sleep still because in theory, sleep() function has no way of knowing the existance of SIGINT(has been cleared)
Your understanding is correct.
Think about it. The process is blocked in the kernel. We need to return to user space to run the handler. How can we do that without interrupting whatever blocking kernel call was running? We only have one process/thread context to work with here. The process can't be both sleeping and running a signal handler.
The sequence is:
Q1: the kernel checks if the process has blocked the received signal, if so, it updates the pending signal bit (unreliable, on systems with relable signals, this should be a counter) in the process entry, for the signal handler to be called when signals are unblocked again (see below). If not blocked, the system call prepares the return value and
errno
value and returns to user mode with a special code installed in the program's virtual stack that makes it to call the signal handler (already in user mode) before returning from the genericsyscall
code. The return from the system call gives-1
to the caller code, and theerrno
variable is set toEINTR
. This requires the process to have installed a signal handler, because by default the action is to abort the process, so it will not return from the system call it is waiting on. Think that when one says the kernel the actual code executed is in the system call being awaken and notified of the special condition (a signal received) The interrupted call, detects that a signal handler is to be called, and prepares the user stack to jump to the proper place (the interrupt handler in user code) before returning from thesyscall()
wrapper.Q2: pending bit is only used to save that a pending signal handler is to be called, so this is not the case. In the execution part of the process, the unix program loader installs some basic code to jump to the signal handler before returning from the system call. This is because the signal handler has to execute in user mode (not in kernel mode) so everything happens upon termination of system call. The signal handler executed is the
SIGINT
, but the code interrupted is a system call, and nothing happens until the system call returns (with the return code and theerrno
variable already fixed)Q3: well, your reasoning was based on a wrong premise, that is, the interrupt pending flag is indicating that an interrupt has been received. This bit only signals that an unprocessed interrupt has been marked for delivery as soon as you unblock it, and this only happens in another system call (to unblock a signal). As soon as the signal is unblocked, the return code of the
sigsetmask(2)
syscall will execute the signal handler. In this case, the signal will be delivered to the process as soon as the timer elapses, the system call will be interrupted and, if you have not installed a signal handler for theSIGALRM
signal (butsleep(2)
implementation does this ---at least, old implementations did) the program will be aborted.NOTE
When I say that the program is aborted by the kernel but in both cases, the signals involved (
SIGINT
andSIGALRM
) don't make it to dump a core file. The program is aborted without generatingcore
. This is different to the behaviour of theabort()
routine, which sends aSIGABRT
and so, it makes de kernel to dump a core file of the process.Imagine the
sleep()
function in the kernel as a function that:0
if the time expired)sleep()
was interrupted by a signal instead of returning to the code that calledsleep()
)Also imagine that there's a second function (that I'm going to call
wake()
for no particular reason) that:Naturally, if the timer's IRQ handler notices that the "timer event" has expired then the timer's IRQ handler would call the
wake()
function to wake the task up again.Now imagine there's a third function (that I'm going to call
send_signal()
) which might be called by other functions (e.g. called bykill()
). This function might set a "pending signal" flag for the task that's supposed to receive the signal, then check what state the receiving task is in; and if the receiving task is in the "SLEEPING" state it calls thewake()
function to wake it up (and then lets the latter part of thesleep()
function worry about delivering the signal back to user-space whenever the scheduler feels like giving the task CPU time later).