Windows 子进程 c++ 的控制台信号处理

Windows console signal handling for subprocess c++

假设我们有一个用 C++ 编写的小程序,如下所示。
该程序本身有意不通过 WinAPI 调用执行信号处理SetConsoleCtrlHandler - 这是问题的重要部分。

#include <stdio.h>
#include <stdlib.h>

int main() {
  while(true) {
    int status = system("EXTERNAL COMMAND");
    printf("RESULT STATUS = %d\n", status);
  }
}

当在终端中按下 Ctrl+C 组合键时,上面的程序会有完全不同的行为,这取决于调用了哪个 "EXTERNAL COMMAND"

1) 如果外部命令是pause,程序会陷入死循环,一步步调用pause命令,并打印"RESULT STATUS = 0" many times,但不会被进程强制终止杀.
2) 如果choice中有外部命令,程序会在Ctrl+C压力后立即终止。它不会打印任何内容,也不会从 system 调用中 return。
3) 如果外部命令是set /P VAR=,程序会有很多有趣的行为。当按下 Ctrl+C 时,程序打印 `"RESULT STATUS = 1" 并继续工作直到执行第一个异步调用。

第一种情况和第二种情况可以用以下方式解释。终端 windows 位于用户输入和目标程序之间,因此当用户按下 Ctrl+C 时,终端 window 会自行向目标进程发送信号。
所以一些子进程可以通过 hConsole = GetStdHandle(STD_OUTPUT_HANDLE) 手动获取终端处理程序并执行自己的信号处理。另一个子进程不这样做,所以信号传递给父进程并终止它。

但是第三种情况引起了很大的问题。如果子进程拦截 SIGINT,为什么父进程在第一次异步调用后执行终止。如果不是,为什么它不立即终止,为什么以及如何打印 `"RESULT STATUS = 1" 并继续工作。

谢谢

Windows 中没有 Unix 信号,至少不是来自内核。也就是说,Windows 和 Windows API 基本上是基于 C 编程语言的,而 C 编程语言是与 Unix 一起开发的,确实需要 six signals。 Windows 中的 C 运行时在进程内模拟 SIGABRTSIGTERM(例如与 C raise 一起使用)。对于 SIGSEGVSIGILLSIGFPE,它使用 OS 异常处理程序。在控制台应用程序中,标准 SIGINT 和非标准 SIGBREAK 与 C 运行时的控制台控制处理程序关联,通常是通过 SetConsoleCtrlHandler 注册的第一个处理程序。 CTRL_C_EVENT 映射到 SIGINT 信号处理程序,所有其他(CTRL_BREAK_EVENTCTRL_CLOSE_EVENT)映射到 SIGBREAK 处理程序。

控制台控制事件由控制台主机 (conhost.exe) 发送,它通过让会话服务器 (csrss.exe) 在客户端进程中创建一个线程来实现。该线程从 kernelbase.dll 中未记录的 CtrlRoutine 函数开始,它遍历已注册的控制处理程序,直到其中一个通过返回 true 来处理事件。如果其中 none 个处理了该事件,则默认处理程序调用 ExitProcess(STATUS_CONTROL_C_EXIT)。请注意,SetConsoleCtrlHandler(NULL, TRUE) 设置了一个标志,使 CtrlRoutine 忽略 CTRL_C_EVENT,并且此标志由子进程继承,并在使用标志 CREATE_NEW_PROCESS_GROUP 创建进程时默认启用。此外,对于 CTRL_CLOSE_EVENT,会话服务器给每个进程 5 秒的时间来处理事件并自行退出,否则它会强制终止进程。

要了解 CMD 的内部 PAUSE 命令发生了什么,请参阅 SetConsoleMode,尤其是 ENABLE_PROCESSED_INPUTPAUSE 调用 C _getch,它临时将控制台输入模式设置为 0。在禁用处理输入模式的情况下,Ctrl+C 被简单地读取为“\x03”,而不是生成 CTRL_C_EVENT.