【问题标题】:How to prevent SIGINT in child process from propagating to and killing parent process?如何防止子进程中的 SIGINT 传播到父进程并杀死父进程?
【发布时间】:2024-01-14 21:24:01
【问题描述】:

我最近学会了如何做整个 fork/exec/wait 事情,最后写了一些看起来像这样精简的代码:

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

int main() {
  pid_t x = fork();
  if (x == 0) {
    execlp("sqlite3", "sqlite3");
    printf("exec failed\n");
  } else {
    wait(NULL);
    printf("Hi\n");
  }
}

这很有效,实际上最终打开了sqlite3 shell,但有一个问题。如果您在sqlite3 shell 之外Ctrl + C,那么它也会终止父进程并且printf("Hi\n") 行永远不会运行。

我认为这是因为 SIGINT 正在传播到父进程,在进一步研究之后,我读到 SIGINT 将终止同一组中的所有进程,这意味着由于父进程和子进程共享同一个组'都被终止了。

我试图通过像这样调用setpgid 来解决这个问题:

int main() {
  pid_t x = fork();
  if (x == 0) {
    setpgid(getpid(), getpid()); // <-- Added in this line
    execlp("sqlite3", "sqlite3");
    printf("exec failed\n");
  } else {
    wait(NULL);
    printf("Hi\n");
  }
}

运行良好,但打破了sqlite3 提示。此外Ctrl + C 在这种情况下仍然杀死了父母。我决定尝试进一步缩小范围,然后发现一些奇怪的东西,并被难住了。只需在 fork 中调用 getpid() 就会使 execlp() 失败。

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

int main() {
  pid_t x = fork();
  if (x == 0) {
    getpid(); // <--- just adding this in makes it fail
    execlp("sqlite3", "sqlite3");
    printf("exec failed\n");
  } else {
    wait(NULL);
    printf("Hi\n");
  }
}

不仅适用于sqlite3,还适用于execlp("ls", "ls") 之类的简单内容(尽管这可能与原始问题无关)。

我有点不知所措,我想知道是否有人能指出我正确的方向。我不太确定从这里做什么。

  • 我使用的是 OSX 10.11,并且正在使用 clang 进行编译。
  • clang --version:Apple LLVM 版本 8.0.0 (clang-800.0.42.1)

【问题讨论】:

  • "如果你 Ctrl + C 退出 sqlite3 shell,那么它也会终止父进程" 因为 Ctrl+C 会导致 Bash 向它产生的进程发送 SIGINT (即您的 C 程序),而不是当时恰好从标准输入读取的任何进程。
  • @ColonelThirtyTwo 嗯,我明白了!所以我需要将Ctrl + C 发送到前台?另外,当我做execlp("bash", "bash") 时,为什么不是这种情况?处理Ctrl + C 就好了。
  • 你已经是了。就 bash 而言,您的进程是前台进程,而 sqlite3 只是碰巧使用相同标准输入管道的其他进程。您可以在 C 程序中处理 ctrl+c 并发送 sqlite3 SIGTERM,或使用 Ctrl+D 将 EOF 发送到 stdin,sqlite3 将接收并解释为 stdin 流的结尾。
  • 首先修复最严重的错误:它应该是execlp("bash", "bash", NULL); -- 需要 NULL 来终止execl()execlp() 和的参数列表execle()。然后,如果你想在父进程中处理 Ctrl+C,只需安装一个信号处理程序,它将SIGINT 信号转发给子进程。
  • 我想 bash 对终端做了一些特殊的设置来捕获和解释 ctrl+c 以便它可以将 SIGINT 发送给它的孩子。子 bash 进程会覆盖父进程的设置。

标签: c linux process sqlite fork


【解决方案1】:

您可以捕获 SIGINT 或编写信号处理程序。 使用下面的代码:

signal(SIGINT, sig_handler);
void sig_handler(int signo)
{
  if (signo == SIGINT)
    printf("received SIGINT\n");
}

【讨论】: