信号处理程序无限循环:为什么?

Sha*_*hay 2 c ubuntu signals windows-subsystem-for-linux

编辑:
\n我在第一次接触 signal.h 后就匆忙地写了这样的内容,所以我最初的问题有点到处都是,并且被很多更混乱的部分所困扰,这可能会让它变得更烦人/更困难回答。我似乎还误解了信号 \xe2\x80\x93 的“屏蔽”部分,我似乎认为屏蔽意味着信号将被丢弃,但实际上,我认为发生的情况是处理程序是\没有被触发,但信号传输并“保持”(重复的信号被合并),直到从处理程序返回。
\n在这里重新措辞以突出显示我原始问题中仍然让我困惑的部分(原始问题仍然在下面,以免在没有明确上下文的情况下留下现有评论等):

\n

据我所知,信号处理程序是在内核模式到用户模式转换时触发的。引用:
\n男人7号信号摘录
\n(万一图像损坏,无论出于何种原因\xe2\x80\x93,它引自man 7 signal:“每当从内核模式转换到用户模式执行时[...]内核检查是否有一个待处理的未阻塞信号,进程已为其建立了信号处理程序。”)

\n

我写下了下面的代码:

\n
const char* ON_SIGTSTP_ENTRY = "Got SIGTSTP.\\n";\nconst char* ON_SIGTSTP_EXIT = "Exiting SIGTSTP handler.\\n";\nconst char* ON_SIGINT_ENTRY = "Got SIGINT.\\n";\nconst char* ON_SIGINT_EXIT = "Exiting SIGINT handler.\\n";\n\nvoid ctrlZHandler(int sig_num) {\n    write(1, ON_SIGTSTP_ENTRY, strlen(ON_SIGTSTP_ENTRY));\n    kill(getpid(), SIGINT);\n    write(1, ON_SIGTSTP_EXIT, strlen(ON_SIGTSTP_EXIT));\n}\n\nvoid ctrlCHandler(int sig_num) {\n    write(1, ON_SIGINT_ENTRY, strlen(ON_SIGINT_ENTRY));\n    kill(getpid(), SIGTSTP);\n    write(1, ON_SIGINT_EXIT, strlen(ON_SIGINT_EXIT));\n}\n\nint main() {\n    if(signal(SIGTSTP, ctrlZHandler) == SIG_ERR) {\n        cout << "Failed to set SIGTSTP handler." << endl;\n    }\n\n    if(signal(SIGINT, ctrlCHandler) == SIG_ERR) {\n        cout << "Failed to set SIGINT handler." << endl;\n    }\n\n    kill(getpid(), SIGTSTP);\n\n    cout << "Returning from main." << endl;\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

输出无限遵循以下模式:

\n
Got SIGTSTP.\nGot SIGINT.\nExiting SIGINT handler. \nExiting SIGTSTP handler.\nGot SIGTSTP.\nGot SIGINT.\nExiting SIGINT handler. \nExiting SIGTSTP handler.\n
Run Code Online (Sandbox Code Playgroud)\n

这对我来说有点奇怪,因为这意味着发生的情况是我们进入 SIGTSTP 处理程序,然后在其中进入 SIGINT 处理程序,然后我们退出 \xe2\x80\x93 ,然后立即再次触发,而不使与其余部分的任何进展main()
\n我猜测会发生的情况更类似于我们得到其中一个 sigtstp-into-sigint 周期,然后到达 main 中的打印,这反过来触发内核模式-用户模式转换,从而触发另一个信号周期,在这一点之后我们从 main 返回(可能还有几个周期,因为在 main 结束后开始触发更多的系统调用)。
\n但是,似乎 main 中的打印从未真正到达:有点像我们从信号处理程序返回后立即触发下一个信号处理程序,但据我了解,从函数返回通常不应触发用户模式到内核模式的转换(尽管信号处理程序可能不同?)。

\n

希望对这种现象以及我对此的误解做出解释。

\n
\n

原来的:

\n

据我了解,在我们从某个信号处理程序返回之前,调用它的相同类型的信号都会被屏蔽;我们不会再次触发相同的信号处理程序。
\n基于此,我尝试了如下操作:
\n我将处理程序注册到两个不同的信号(SIGFPE 和 SIGINT,如果这很重要),并在这两个信号中的每一个中,将另一个信号发送到进程。也就是说,类似:

\n
void fpe_handler (int signum) {\n    kill(getpid(), SIGINT);\n    printf("fpe\\n");\n}\n\nvoid int_handler (int signum) {\n    kill(getpid(), SIGFPE);\n    printf("int\\n");\n}\n
Run Code Online (Sandbox Code Playgroud)\n

之后,我得到了 using 触发的信号sleep(3);,这样我们就可以在后台触发一些系统调用来启动内核模式到用户模式的切换并触发可用的处理程序,然后我打开另一个 bash 实例并向我的进程发送了一个 SIGFPE。
\n此时发生的事情是我无法向自己解释的:我的过程已进入无限循环。
\n这让我感到困惑的原因是我打开这个问题的“理解”。当我试图在运行代码之前预测会发生什么时,我想:我触发了 SIGFPE 的处理程序 fpe_handler;此时,kill调用触发SIGINT的处理程序int_handler;这再次发送一个 SIGFPE,但 fpe_handler 的运行尚未结束,因此此时这不会产生效果 \xe2\x80\x93 此时 int_handler 将返回,将执行传递给fpe_handler 的 printf,然后返回 main。也许由于 libc 恶作剧,还会有另外一两个系统调用,但即便如此,也只是有限数量的打印。
\n然后我想:也许实际发生的情况是,我不会返回到 fpe_handler 的 printf,而是返回到 fpe_handler 的 \xe2 kill\x80\x93,从而触发 int_handlers 的无限循环,因为 fpe_handler永远不会结束,因此永远不会再次被调用,但是没有什么可以阻止我们一次又一次地进入和退出 int_handler 。

\n

然而,这两种情况都没有发生:输出不是重复的int消息,而是重复的alternatinvfpeint消息,这意味着fpe_handler确实由于int_handler的调用而一次又一次地被触发kill

\n

为什么会出现这种情况?我有什么误解吗?

\n

And*_*nle 5

未忽略的信号可以在信号处理程序执行时被阻止,但在信号处理程序返回后它们将不再被阻止。

此类信号在被阻止时保持待处理状态(请注意,尽管同一类型的多个信号可能会合并为单个信号),并且在不再被阻止后将被传递到进程。

根据2.4.1 信号生成和传送

...

在信号生成与其传送或接受之间的时间期间,该信号被称为“待处理”。通常,应用程序无法检测到此间隔。但是,信号可能会被“阻止”传递到线程。如果与阻塞信号相关的操作不是忽略该信号,并且如果该信号是为线程生成的,则该信号应保持挂起状态,直到它被解除阻塞为止,...

一旦挂起的信号被解除阻塞,它将被传递。