Linux如何优先考虑自定义信号处理程序?

时间:2021-03-14 08:12:23

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:

我们上周进行了一次演讲,其中涉及操作系统(在本例中为Linux,在本例中我们的学校服务器使用SUSE Linux 11)如何处理中断。需要注意的是,对于大多数信号,您可以捕获中断并定义自己的信号处理程序来运行而不是默认值。我们用一个例子来说明这一点,我发现起初我认为这是一种有趣的行为。这是代码:

#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:

所以,在运行此代码时,我期望这样的事情:

  1. Running code.... ^C
  2. 运行代码.... ^ C.

  3. Enter inthandler, executing loop, hit ^\
  4. 输入inthandler,执行循环,点击^ \

  5. Exit inthandler, go into quithandler, execute quithandler loop
  6. 退出inthandler,进入quithandler,执行quithandler循环

  7. ^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.
  8. ^ C回到inthandler。如果我在inthandler中再次执行^ C,则忽略连续的inthandler信号,直到当前的inhanndler完成处理。

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:

我发现基于观察的东西看起来像是一个嵌套的,2队列深度的“调度”信号。例如,如果我快速连续输入以下中断:

  • ^C, ^\, ^C, ^\, ^\, ^C
  • ^ 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:

换句话说,它似乎是这样处理的:

  1. Receive first ^C signal
  2. 接收第一个^ C信号

  3. Receive ^\ signal, "delay" the inthandler and go into quithandler
  4. 接收^ \信号,“延迟”inthandler并进入quithandler

  5. Receive next ^C signal, but because we are "nested" in a inthandler already, put it at the back of the inthandler "queue"
  6. 接收下一个^ C信号,但因为我们已经“嵌套”在inthandler中,所以把它放在inhanndler“队列”的后面

  7. Receive quithandler, place at back of quithandler queue.
  8. 接收quithandler,放在quithandler队列的后面。

  9. Execute quithandler until queue is empty. Ignore the third quithandler because it seems to only have a queue depth of 2.
  10. 执行quithandler直到queue为空。忽略第三个quithandler,因为它似乎只有2的队列深度。

  11. Leave quithandler, and execute the 2 remaining inthandlers. Ignore the final inthandler because queue-depth of 2.
  12. 离开quithandler,并执行剩下的2个inthandler。忽略最后的inhanndler,因为队列深度为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.

我向我的教授展示了这种行为,他似乎同意“嵌套的2队列深度”行为正在发生的事情,但我们并不是100%肯定为什么(他来自硬件背景而且刚刚开始教授这门课程) )。我想发布SO以了解是否有人可以阐明Linux处理这些信号的原因和方式,因为我们并不期待某些行为,即嵌套。

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:

我认为我写的测试用例应足以说明正在发生的事情,但这里有一些其他测试用例的截图:

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

I wanted to leave the additional test cases as a link as they're kind of large screenshots.

我想将其他测试用例作为链接,因为它们是一种大型屏幕截图。

Thank you!

2 个解决方案

#1


8  

The rules (for non-realtime signals, such as the SIGQUIT and SIGINT you're using) are:

规则(对于非实时信号,例如您正在使用的SIGQUIT和SIGINT)是:

  1. By default, a signal is masked when its handler is entered, and unmasked when the handler exits;
  2. 默认情况下,在输入处理程序时屏蔽信号,在处理程序退出时屏蔽信号;

  3. If a signal is raised while it is masked, it is left pending, and will be delivered if/when that signal is unmasked.
  4. 如果信号在被屏蔽时被引发,则它将保持挂起状态,并且如果/当该信号未被屏蔽时将被传送。

The pending state is binary - a signal is either pending or not pending. If a signal is raised multiple times while masked, it will still only be delivered once when unmasked.

挂起状态是二进制 - 信号处于挂起或未挂起。如果在屏蔽的情况下多次引发信号,则在取消屏蔽时仍然只会传递一次。

So what happens in your example is:

那么你的例子中发生的事情是:

  1. SIGINT is raised and the inthandler() signal handler starts executing, with SIGINT masked.
  2. 引发SIGINT并且inthandler()信号处理程序开始执行,并且SIGINT被屏蔽。

  3. SIGQUIT is raised, and the quithandler() signal handler starts executing (interrupting inthandler()) with SIGQUIT masked.
  4. 引发SIGQUIT,并且quithandler()信号处理程序开始执行(使用SIGQUIT屏蔽中断inthandler())。

  5. SIGINT is raised, adding SIGINT to the set of pending signals (because it is masked).
  6. 引发SIGINT,将SIGINT添加到待处理信号集(因为它被屏蔽)。

  7. SIGQUIT is raised, adding SIGQUIT to the set of pending signals (because it is masked).
  8. 引发SIGQUIT,将SIGQUIT添加到待处理信号集中(因为它被屏蔽)。

  9. SIGQUIT is raised, but nothing happens because SIGQUIT is already pending.
  10. 引发了SIGQUIT,但由于SIGQUIT已经挂起,所以没有任何反应。

  11. SIGINT is raised, but nothing happens because SIGINT is already pending.
  12. 引发了SIGINT,但没有任何反应,因为SIGINT已经挂起。

  13. quithandler() finishes executing, and SIGQUIT is unmasked. Because SIGQUIT is pending, it is then delivered, and quithandler() starts executing again (with SIGQUIT masked again).
  14. quithandler()完成执行,SIGQUIT被取消屏蔽。因为SIGQUIT处于挂起状态,所以它会被传递,并且quithandler()会再次开始执行(再次屏蔽SIGQUIT)。

  15. quithandler() finishes executing for the second time, and SIGQUIT is unmasked. SIGQUIT is not pending, so inthandler() then resumes executing (with SIGINT still masked).
  16. quithandler()第二次完成执行,并且SIGQUIT被取消屏蔽。 SIGQUIT没有挂起,所以inthandler()然后恢复执行(SIGINT仍然被屏蔽)。

  17. inthandler() finishes executing, and SIGINT is unmasked. Because SIGINT is pending, it is then delivered, and inthandler() starts executing again (with SIGINT masked again).
  18. inthandler()完成执行,并且SIGINT被取消屏蔽。因为SIGINT处于挂起状态,所以它会被传递,并且inthandler()会再次开始执行(再次屏蔽SIGINT)。

  19. inthandler() finishes executing for the second time, and SIGINT is unmasked. The main function then resumes executing.
  20. inhanndler()第二次完成执行,并且SIGINT被取消屏蔽。然后主要功能继续执行。

On Linux, you can see the current set of masked and pending signals for a process by examining /proc/<PID>/status. The masked signals are shown in the SigBlk: bitmask and the pending signals in the SigPnd: bitmask.

在Linux上,您可以通过检查/ proc / / status来查看进程的当前屏蔽和挂起信号集。屏蔽信号显示在SigBlk:位掩码和SigPnd:位掩码中的挂起信号中。

If you install your signal handlers with sigaction() rather than signal(), you can specify the SA_NODEFER flag to request that the signal isn't masked while its handler executes. You could try this in your program - with one or both signals - and try to predict what the output will look like.

如果使用sigaction()而不是signal()安装信号处理程序,则可以指定SA_NODEFER标志,以请求在处理程序执行时不屏蔽信号。您可以在程序中尝试这个 - 使用一个或两个信号 - 并尝试预测输出的样子。

#2


3  

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

我在联机帮助信号(7)中发现了这一点似乎相关:

Real-time signals are delivered in a guaranteed order. Multiple real-time signals of the same type are delivered in the order they were sent. If different real-time signals are sent to a process, they are delivered starting with the lowest-numbered signal. (I.e., low-numbered signals have highest priority.) By contrast, if multiple standard signals are pending for a process, the order in which they are delivered is unspecified.

实时信号以保证顺序传送。相同类型的多个实时信号按发送顺序传送。如果向进程发送不同的实时信号,则从编号最小的信号开始传送它们。 (即,低编号信号具有最高优先级。)相反,如果多个标准信号等待进程,则未指定它们的传递顺序。

Looking at sigprocmask and sigpending documentation, in addition to signal (7), should enhance your understanding of the guarantees regarding pending signals.

除了signal(7)之外,查看sigprocmask和sigpending文档应该可以增强您对待处理信号保证的理解。

To move from the weak "unspecified" guarantee to what actually happens on your version of OpenSUSE, you'd probably need to inspect the signal delivery code in the kernel.

要从弱的“未指定”保证转移到您的OpenSUSE版本上实际发生的事情,您可能需要检查内核中的信号传递代码。

#1


8  

The rules (for non-realtime signals, such as the SIGQUIT and SIGINT you're using) are:

规则(对于非实时信号,例如您正在使用的SIGQUIT和SIGINT)是:

  1. By default, a signal is masked when its handler is entered, and unmasked when the handler exits;
  2. 默认情况下,在输入处理程序时屏蔽信号,在处理程序退出时屏蔽信号;

  3. If a signal is raised while it is masked, it is left pending, and will be delivered if/when that signal is unmasked.
  4. 如果信号在被屏蔽时被引发,则它将保持挂起状态,并且如果/当该信号未被屏蔽时将被传送。

The pending state is binary - a signal is either pending or not pending. If a signal is raised multiple times while masked, it will still only be delivered once when unmasked.

挂起状态是二进制 - 信号处于挂起或未挂起。如果在屏蔽的情况下多次引发信号,则在取消屏蔽时仍然只会传递一次。

So what happens in your example is:

那么你的例子中发生的事情是:

  1. SIGINT is raised and the inthandler() signal handler starts executing, with SIGINT masked.
  2. 引发SIGINT并且inthandler()信号处理程序开始执行,并且SIGINT被屏蔽。

  3. SIGQUIT is raised, and the quithandler() signal handler starts executing (interrupting inthandler()) with SIGQUIT masked.
  4. 引发SIGQUIT,并且quithandler()信号处理程序开始执行(使用SIGQUIT屏蔽中断inthandler())。

  5. SIGINT is raised, adding SIGINT to the set of pending signals (because it is masked).
  6. 引发SIGINT,将SIGINT添加到待处理信号集(因为它被屏蔽)。

  7. SIGQUIT is raised, adding SIGQUIT to the set of pending signals (because it is masked).
  8. 引发SIGQUIT,将SIGQUIT添加到待处理信号集中(因为它被屏蔽)。

  9. SIGQUIT is raised, but nothing happens because SIGQUIT is already pending.
  10. 引发了SIGQUIT,但由于SIGQUIT已经挂起,所以没有任何反应。

  11. SIGINT is raised, but nothing happens because SIGINT is already pending.
  12. 引发了SIGINT,但没有任何反应,因为SIGINT已经挂起。

  13. quithandler() finishes executing, and SIGQUIT is unmasked. Because SIGQUIT is pending, it is then delivered, and quithandler() starts executing again (with SIGQUIT masked again).
  14. quithandler()完成执行,SIGQUIT被取消屏蔽。因为SIGQUIT处于挂起状态,所以它会被传递,并且quithandler()会再次开始执行(再次屏蔽SIGQUIT)。

  15. quithandler() finishes executing for the second time, and SIGQUIT is unmasked. SIGQUIT is not pending, so inthandler() then resumes executing (with SIGINT still masked).
  16. quithandler()第二次完成执行,并且SIGQUIT被取消屏蔽。 SIGQUIT没有挂起,所以inthandler()然后恢复执行(SIGINT仍然被屏蔽)。

  17. inthandler() finishes executing, and SIGINT is unmasked. Because SIGINT is pending, it is then delivered, and inthandler() starts executing again (with SIGINT masked again).
  18. inthandler()完成执行,并且SIGINT被取消屏蔽。因为SIGINT处于挂起状态,所以它会被传递,并且inthandler()会再次开始执行(再次屏蔽SIGINT)。

  19. inthandler() finishes executing for the second time, and SIGINT is unmasked. The main function then resumes executing.
  20. inhanndler()第二次完成执行,并且SIGINT被取消屏蔽。然后主要功能继续执行。

On Linux, you can see the current set of masked and pending signals for a process by examining /proc/<PID>/status. The masked signals are shown in the SigBlk: bitmask and the pending signals in the SigPnd: bitmask.

在Linux上,您可以通过检查/ proc / / status来查看进程的当前屏蔽和挂起信号集。屏蔽信号显示在SigBlk:位掩码和SigPnd:位掩码中的挂起信号中。

If you install your signal handlers with sigaction() rather than signal(), you can specify the SA_NODEFER flag to request that the signal isn't masked while its handler executes. You could try this in your program - with one or both signals - and try to predict what the output will look like.

如果使用sigaction()而不是signal()安装信号处理程序,则可以指定SA_NODEFER标志,以请求在处理程序执行时不屏蔽信号。您可以在程序中尝试这个 - 使用一个或两个信号 - 并尝试预测输出的样子。

#2


3  

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

我在联机帮助信号(7)中发现了这一点似乎相关:

Real-time signals are delivered in a guaranteed order. Multiple real-time signals of the same type are delivered in the order they were sent. If different real-time signals are sent to a process, they are delivered starting with the lowest-numbered signal. (I.e., low-numbered signals have highest priority.) By contrast, if multiple standard signals are pending for a process, the order in which they are delivered is unspecified.

实时信号以保证顺序传送。相同类型的多个实时信号按发送顺序传送。如果向进程发送不同的实时信号,则从编号最小的信号开始传送它们。 (即,低编号信号具有最高优先级。)相反,如果多个标准信号等待进程,则未指定它们的传递顺序。

Looking at sigprocmask and sigpending documentation, in addition to signal (7), should enhance your understanding of the guarantees regarding pending signals.

除了signal(7)之外,查看sigprocmask和sigpending文档应该可以增强您对待处理信号保证的理解。

To move from the weak "unspecified" guarantee to what actually happens on your version of OpenSUSE, you'd probably need to inspect the signal delivery code in the kernel.

要从弱的“未指定”保证转移到您的OpenSUSE版本上实际发生的事情,您可能需要检查内核中的信号传递代码。