How does Linux prioritize custom signal handlers?

We had a lecture last week that talked about how the OS (in this case Linux, and in this particular case, our school server uses SUSE Linux 11) handles interrupts. It should be noted that for most signals, you can catch an interrupt and define your own signal handler to trigger instead of the default value. We used an example to illustrate this, and I found what seemed like interesting behavior at first. Here is 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, running a loop, press ^ \
  • Exit inthandler, go to quithandler, execute the quithandler loop
  • ^ C back to inthandler. If I execute ^ C again while I enter the inthandler, ignore consecutive inthandler signals until the current inthandler is processed.

I found something that, based on observation, looks like nested “planning” of two-line “signals” of signals. If, for example, I quickly introduce the following interrupts:

  • ^ C, ^ \, ^ C, ^ \, ^ \, ^ C

I get the following behavior / output from 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 looks like this:

  • Get the first signal ^ C
  • Get ^ \ signal, "hold" inthandler and go to quithandler
  • Get the next ^ C signal, but since we are already "nested" in the inthandler, put it at the end of the inthandler "queue"
  • Gets the quithandler, a place at the end of the quithandler queue.
  • Execute quithandler until the queue is empty. Ignore the third quithandler because it only has a queue depth of 2.
  • Leave the quithandler and complete the 2 remaining inthandslers. Ignore the final inthandler because the queue depth is 2.

I showed the behavior to my professor, and he seems to agree that the behavior of the "nested 2nd stage" is what happens, but we are not 100% sure why (it comes from the hardware background and just started teaching this class). I wanted to post to SO to find out if anyone can shed some light on why / how Linux processes these signals, since we did not expect any behavior, i.e. nesting.

I think that the test script I wrote should be sufficient to illustrate what is happening, but here are a bunch of screenshots from additional test cases:

http://imgur.com/Vya7JeY,fjfmrjd.30YRQfk,uHHXFu5,Pj35NbF

I wanted to leave additional test cases as a reference, as they are great screenshots.

Thanks!

+5
source share
2 answers

Rules (for non-real-time signals such as SIGQUIT and SIGINT that you use):

  • By default, the signal is masked when its handler is entered and exposed when the handler exits;
  • If a signal is raised when it is masked, it remains on hold and will be delivered if / when this signal is open.

The pending state is binary - the signal is waiting or not waiting for a response. If the signal rises several times during masking, it will still be delivered once when the mask is removed.

So what happens in your example:

  • SIGINT occurs, and the inthandler() signal handler starts execution, with SIGINT hiding.
  • SIGQUIT , and the quithandler() signal handler starts execution (interruption inthandler() ) with masked SIGQUIT .
  • SIGINT by adding SIGINT to the set of pending signals (because it is masked).
  • SIGQUIT by adding SIGQUIT to the set of pending signals (because it is masked).
  • SIGQUIT arises, but nothing happens because SIGQUIT already expecting.
  • SIGINT is created, but nothing happens because SIGINT already expecting.
  • quithandler() finishes execution, and SIGQUIT exposed. Since SIGQUIT is waiting, it is then delivered, and quithandler() starts execution again (with SIGQUIT masked again).
  • quithandler() completes the second time, and SIGQUIT disabled. SIGQUIT not expected, so inthandler() then resumes execution (with SIGINT still masked).
  • inthandler() finishes execution, and SIGINT disabled. Since SIGINT is waiting, it is then delivered, and inthandler() starts execution again (with SIGINT masked again).
  • inthandler() completes the second time, and SIGINT disabled. Then the main function resumes execution.

On Linux, you can see the current set of masked and pending signals for a process by looking at /proc/<PID>/status . Masked signals are displayed in the SigBlk: and the pending signals in the SigPnd: layout.

If you install signal handlers using sigaction() rather than signal() , you can specify the SA_NODEFER flag to request that the signal not be masked during its execution. You can try this in your program - with one or both signals - and try to predict how the result will look.

+8
source

I found this in manpage signal (7) , which seems relevant:

Signals in real time are transmitted in a guaranteed manner. Multiple real-time signals of the same type are delivered in the order they were sent. If different signals in real time are sent to the process, they are delivered starting with the lowest signal number. (That is, the signals with the lowest number have the highest priority.) Contrast, if several standard signals are expected for the process, the order in which they are delivered is not specified.

The sigprocmask and sigpending in addition to signal (7) should improve understanding of guarantees regarding pending signals.

To move from a weak “unspecified” guarantee of what is actually happening in your version of OpenSUSE, you probably need to check the signal delivery code in the kernel.

+3
source

All Articles