我怎样才能捕获 SIGINT 并让它只杀死 C 中的前台进程?

How can I catch SIGINT and have it only kill foreground processes in C?

我正在创建一个 shell 程序,我希望 Ctrl+C 终止前台进程而不是后台进程过程例如; &sleep 50 是一个后台进程,我希望它是这样的,如果我使用 Ctrl+C 它会杀死任何前台进程,而后台不受影响。但是对于我这辈子都不知道该怎么做,非常感谢任何帮助:D

int main(void) {
  Command cmd;
  int n, forSignals;
  char **cmds;
  char **pipedCmds;
  signal(SIGINT, INThandler);
  while (!done) {
    char *line;
    line = readline("> ");
    if (!line) {
      done = 1;
    } else {
      stripwhite(line);
      if(*line) {
        add_history(line);
        n = parse(line, &cmd);
        PrintCommand(n, &cmd);
        cmds = getCmds(cmd.pgm);
        pipedCmds = getPipedCmds(cmd.pgm);
        executionDecider(line, cmds, pipedCmds, cmd);
      }
    }
    if(line) {
      free(line);
    }
  }
  return 0;
}

void  INThandler(int sig)
{
  signal(sig, SIG_IGN);
  kill(SIGINT, 0);
  printf("\n");
}

P.S。当然还有实际执行程序的其余代码,让我知道是否有必要显示,但我相信,这是一个很好的最小可重现示例。

编辑:非常重要,不知道我是怎么忘记的:/但我需要它不通过这样做创建僵尸进程,它不应该留下任何僵尸。

编辑:请找到指向整个项目代码转储的链接 URL。那里可能更有意义: https://codedump.io/share/d8hrj40JdEqL/1/lshc---c-shell-program

默认情况下禁用所有后台进程的标准输入。 您可以使用 sudo ls -al /proc/<"pid">/fd 检查。 在那种情况下,任何输入都无法从终端传输到后台进程。 所以即使在 ctrl+c 之后它也不会被终止。并且只有前台进程会被终止。

++

根据手册页 int kill(pid_t pid, int sig); 你在这里做 kill(SIGINT,0); 根据您的代码,第二个参数 0 表示挂断信号(SIGHUP),只有在 shell 被终止时才会出现。它终止 shell 中的所有进程 运行。 更新 kill 函数调用。

++ 您提到了“&sleep 50”,这在您使用的 OS linux.So 中无效。

我假设您通过 kill -2 发送 Ctrl-C,这样您就可以轻松中断后台进程。

您可以通过查看终端前台进程组轻松找出您的进程是运行前台还是后台:

#include <unistd.h>

int isForeground() 
{
    pid_t pid = tcgetpgrp(STDIN_FILENO);
    if(pid == -1 /* piped */ || pid == getpgrp() /* foreground */)
        return 1;
    /* otherwise background */
    return 0;
}

使用该函数来决定是否向 signal() 函数注册您的信号处理程序

if (isForeground())
    // register my custom signal handler
    if (SIG_ERR == signal(SIGINT, mySignalHandler))
        printf("unable to catch SIGINT!\n");

或者在你的信号处理函数中

void handler(int sig)
{
    // background processes shall be left unaffected
    if (0 == isForeground())
        return;

    // otherwise process the signal accordingly, e.g. cleaning things up
    // and using exit() to terminate
    ...
}

这适用于我的 Linux 系统。请注意,signal() 的语义可能因不同的 Unix 系统(BSD、SysV)而异,这可能会影响此代码的可移植性。

我认为你的核心问题是如何插入进程组的标准信号语义。 默认情况下,您 fork() 的所有进程都保留在您所属的进程组中。系统调用 setpgid() 可以创建一个新的进程组,为新进程分离组信号语义。

某些信号被传送到进程组。来自 tty 驱动程序的通知被广播到 tty 的会话领导者当前所属的进程组。简单吧:-?

因此,您要做的是在您开始的 child 个进程中使用 setpgid() 来创建一个新的进程组。它们随后启动的任何进程都将继承它们的新进程组;所以他们没有办法回到原来的根进程组[我想,这里的地面不平坦]。

我编写了一个示例程序,它创建了一组进程,这些进程在收到 SIG_TERM 信号之前一直处于休眠状态。这些进程中的每一个都放在自己的进程组中(浪费资源,但代码小得多)。当您在此程序 运行ning 上点击 Control-C 时,它会宣布其 SIG_INT,然后将 SIG_TERM 传递给它启动的所有进程。当您点击 Control-D 时,主进程退出。

如果你输入Control-C,Control-D,你可以判断这个程序是有效的,你应该发现ps/pgrep没有残差children存在,并且输出屏幕显示接收 SIG_TERM (15).

的正确配置 (NPROC) 进程数
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#ifndef NPROC
#define NPROC 7
#endif
static int children[NPROC];
static int nproc;

static void SigPrint(char *act, int signo) {
    char buf[100];
    int n;
    n = sprintf(buf, "[%d]:(%d) -> %s\n", getpid(), signo, act);
    write(1, buf, n);
}

static void SigDie(int signo) {
    SigPrint("Die", signo);
    _exit(1);
}

static void SigDisp(int signo) {
    SigPrint("Dispatch", signo);
    int i;
    for (i = 0; i < nproc; i++) {
        if (kill(children[i], SIGTERM) < 0) {
            perror("kill");
        }
    }
}

int main(void) {
    signal(SIGINT, SigDisp);
    signal(SIGTERM, SigDie);
    for (nproc = 0; nproc < NPROC; nproc++) {
        children[nproc] = fork();
        if (children[nproc] == 0) {
            signal(SIGINT, SIG_DFL);
            if (setpgid(0, 0) < 0) {
                perror("setpgid");
                _exit(1);
            }
            while (sleep(5)) {
            }
            _exit(0);
        } else if (children[nproc] < 0) {
            perror("fork");
            kill(getpid(), SIGINT);
            perror("kill");
            _exit(1); /*Just in case... */
        }
    }
    int c;
    while ((c = getchar()) != EOF) {
        putchar(c);
    }
    return 0;
}

当你运行这个时,你应该得到类似这样的输出:

^C[15141]:(2) -> Dispatch
[15142]:(15) -> Die
[15143]:(15) -> Die
[15146]:(15) -> Die
[15144]:(15) -> Die
[15145]:(15) -> Die
[15147]:(15) -> Die
[15148]:(15) -> Die

[pid] 字段具有不同的值。

此程序展示了如何从 parents 的通知中分离 child 进程。还可以更好,这里的模型还挺丰富的