【问题标题】:How can I catch SIGINT and have it only kill foreground processes in C?如何捕获 SIGINT 并让它只杀死 C 中的前台进程?
【发布时间】:2020-04-13 12:22:16
【问题描述】:

我正在创建一个 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");
}

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

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

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

【问题讨论】:

  • 我可能遗漏了一些东西,但我认为我们需要在这里看到更多。 UNIX tty 已经 将 Ctrl-C 转换为针对前台进程组的 SIGINT。除了manage job control process groups,你不需要做任何事情。
  • 你能添加Command和其他缺少的功能吗?

标签: c shell signals signal-processing


【解决方案1】:

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

++

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

++ 您提到“&sleep 50”这在 linux 中无效。那么您使用的是哪个操作系统。

【讨论】:

    【解决方案2】:

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

    某些信号被传递到进程组。来自 tty 驱动程序的通知被广播到 tty 的会话负责人当前所属的进程组。很简单,对吧:-?

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

    我编写了一个示例程序,它创建了一组进程,这些进程只是在休眠状态下挂起,直到它们被发送一个 SIG_TERM 信号。这些进程中的每一个都放置在自己的进程组中(浪费资源,但代码要少得多)。当您在该程序运行时按 Control-C 时,它会宣布其 SIG_INT,然后将 SIG_TERM 传递给它启动的所有进程。当你按下 Control-D 时,主进程退出。

    如果您输入 Control-C、Control-D,您可以告诉该程序有效,您应该使用 ps/pgrep 发现不存在残余子级,并且输出屏幕显示正确配置的 (NPROC) 接收 SIG_TERM 的进程数(15)。

    #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] 字段的值不同。

    该程序展示了如何将子进程与父进程的通知分开。可以更好,这里的模型很丰富。

    【讨论】:

    【解决方案3】:

    我假设您通过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)而异,这可能会影响此代码的可移植性。

    【讨论】:

    • 这不是天生的吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-28
    相关资源
    最近更新 更多