We had a lecture last week that involved how the OS (in this case Linux, and in this particular case our school server uses SUSE Linux 11) handles interrupts. One thing of note was that for most signals, you can catch the interrupt and define your own signal handler to run instead of the default. We used an example to illustrate this, and I found what at first seemed to me as interesting behavior. Here's the code:
#include <stdio.h>
#include <signal.h>
#define INPUTLEN 100
main(int ac, char *av[])
{
void inthandler (int);
void quithandler (int);
char input[INPUTLEN];
int nchars;
signal(SIGINT, inthandler);
signal(SIGQUIT, quithandler);
do {
printf("\nType a message\n");
nchars = read(0, input, (INPUTLEN - 1));
if ( nchars == -1)
perror("read returned an error");
else {
input[nchars] = '\0';
printf("You typed: %s", input);
}
}
while(strncmp(input, "quit" , 4) != 0);
}
void inthandler(int s)
{
printf(" Received Signal %d ....waiting\n", s);
int i = 0;
for(int i; i<3; ++i){
sleep(1);
printf("inth=%d\n",i);
}
printf(" Leaving inthandler \n");
}
void quithandler(int s)
{
printf(" Received Signal %d ....waiting\n", s);
for(int i; i<7; ++i){
sleep(1);
printf("quith=%d\n",i);
} printf(" Leaving quithandler \n");
}
So, when running this code, I expected something like this:
- Running code.... ^C
- Enter inthandler, executing loop, hit ^\
- Exit inthandler, go into quithandler, execute quithandler loop
- ^C back to inthandler. If I execute ^C again while I'm in the inthandler, ignore successive inthandler signals until the current inthandler is done processing.
I found something that, based on observation, seems like a nested, 2-queue-depth "scheduling" of the signals. If, for example, I enter the following interrupts in quick succession:
- ^C, ^\, ^C, ^\, ^\, ^C
I'll receive the following behavior/output from the code:
^CReceived signal 2 ....waiting
^\Received Signal 3 ....waiting
^C^\^\^C quith=0
quith=1
quith=2
quith=3
quith=4
quith=5
quith=6
quith=7
Leaving quithandler
Received Signal 3 ....waiting
quith=1
quith=2
quith=3
quith=4
quith=5
quith=6
quith=7
Leaving quithandler
inth=0
inth=1
inth=2
inth=3
Leaving inthandler
Received Signal 2 ....waiting
inth=0
inth=1
inth=2
inth=3
Leaving inthandler
In other words, it appears to be processed like this:
- Receive first ^C signal
- Receive ^\ signal, "delay" the inthandler and go into quithandler
- Receive next ^C signal, but because we are "nested" in a inthandler already, put it at the back of the inthandler "queue"
- Receive quithandler, place at back of quithandler queue.
- Execute quithandler until queue is empty. Ignore the third quithandler because it seems to only have a queue depth of 2.
- Leave quithandler, and execute the 2 remaining inthandlers. Ignore the final inthandler because queue-depth of 2.
I showed the behavior to my professor, and he seems to agree that the "nested 2 queue depth" behavior is what's happening, but we're not 100% sure why (he comes from a hardware background and has only just started teaching this class). I wanted to post to SO to see if anybody could shed some light on why/how Linux processes these signals, as we weren't quite expecting some of the behavior i.e. nesting.
I think the test case I wrote out should be enough to illustrate what's going on, but here are a bunch of screenshots of additional test cases:
https://i.stack.imgur.com/2k6dH.png
I wanted to leave the additional test cases as a link as they're kind of large screenshots.
Thank you!