如何只在前台杀死子进程?

How to only kill the child process in the foreground?

我正在尝试构建一个 shell,并且我已经设法编写了大部分功能,但是我有一个小问题。

假设我输入 firefox &。 Firefox 将作为后台进程打开。 & 激活一个 BG 标志,使父进程不等待子进程。

然后我输入 gedit。 Gedit 将作为前台进程打开。这意味着当前父级正在等待进程关闭。

此时,父进程有两个进程 - firefoxgedit。 Firefox 尚未等待,目前处于后台,而我们目前正在等待 Gedit 完成。到目前为止一切顺利。

但是,如果我决定通过按 ctrl-c 发送 SIGINT 信号,firefoxgedit 都会关闭。不好,只有 gedit 应该关闭。

这是我的信号处理函数:

pid_t suspended_process[10];
int last_suspended = -1;

void signal_handler(int signo){
    pid_t process = currentpid();

    // Catches interrupt signal
    if(signo == SIGINT){
        int success = kill(process, SIGINT);
    }

    // Catches suspend signal
    else if(signo == SIGTSTP){
        int success = kill(process, SIGTSTP);
        resuspended = 1;
        suspended_process[last_suspended+1] = process;
        last_suspended++;
    }
}

这是 fork-exec 代码中等待进程或继续运行的部分。

  else if(pid > 0){ //Parent
    current_pid = pid;

    // Waits if background flag not activated.
    if(BG == 0){
      // WUNTRACED used to stop waiting when suspended
      waitpid(current_pid, &status, WUNTRACED);

        if(WIFEXITED(status)){
          setExitcode(WEXITSTATUS(status));
        }
        else if(WIFSIGNALED(status)){
          printf("Process received SIGNAL %d\n", WTERMSIG(status));
        }
    }
  }

如果我事先挂起进程,也会发生这种情况。比如我运行firefox然后按ctrl-z就暂停了。然后我运行gedit然后按ctrl-c关闭它。紧接着,如果我按 fg 恢复挂起的 firefox,它会立即关闭。

我找不到只将 SIGINT 信号发送到前台进程的方法,它总是将信号发送给父进程以外的所有子进程,无论它们是在后台还是挂起。

以防万一,这是初始化信号处理程序的函数:

void init_handler(){
    struct sigaction sa;

    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    // If conditions for signal handling.
    // Also creates 2 signal handlers in memory for the SIGINT and SIGTSTP
    if(sigaction(SIGINT, &sa, NULL) == -1)
        printf("Couldn't catch SIGINT - Interrupt Signal\n");
    if(sigaction(SIGTSTP, &sa, NULL) == -1)
        printf("Couldn't catch SIGTSTP - Suspension Signal\n");
}

这很简单,但是 signals 并没有完成。相反,您必须使用名为 进程组 的功能。每个 作业 (executable 或管道等)将是一个单独的 进程组 。您可以使用 setpgid(或在某些系统上使用 setpgrp)创建进程组。可以简单的在fork之后exec之前设置子进程的进程组,然后将这个job的进程组id存入jobtable.

现在,前台的进程组被设置为终端的活动进程组(shell 的 /dev/tty)和 tcsetpgrp - 这是将接收 CTRL+C 的进程组。那些属于同一会话但不属于使用 tcsetpgrp 设置为前台的组的进程组将完全忘记 CTRL+C.

在Antti的帮助下,我终于找到了问题所在。我在 fork-exec 代码中添加了一行:

  else if(pid > 0){ //Parent
    current_pid = pid;

    if(setpgid(pid, pid) == 0) perror("setpid");

    // Waits if background flag not activated.
    if(BG == 0){
      // WUNTRACED used to stop waiting when suspended
      waitpid(current_pid, &status, WUNTRACED);

        if(WIFEXITED(status)){
          setExitcode(WEXITSTATUS(status));
        }
        else if(WIFSIGNALED(status)){
          printf("Process received SIGNAL %d\n", WTERMSIG(status));
        }
    }
  }

if(setpgid(pid, pid) == 0) perror("setpid");

据我所知,setpgid 设置进程的进程组 ID。这意味着在上面的行中,我将 pid 为 pid 的进程的 pgid 设置为 pid

我可能是错的,我仍然没有完全理解这个过程,但之所以有效,是因为 SIGINT 信号只发送到 pgid pid。之前的意思是,由于我没有设置每个进程的 pgid,它们都具有相同的 pgid,因此它们都会收到信号。但是,一旦我为每个进程设置 pgid,如果我在前台进程中间按 CTRL-C,它只会退出 that 运行宁进程.

至少这是我能收集到的信息。 tcsetpgrp我还是不太明白,尤其是第一个参数可以设置什么,也就是文件描述符。在 setpgid 之后添加此行:

tcsetpgrp(STDIN_FILENO, pid)

每当我 exec 命令时,只需在后台启动整个程序。而不是 运行ning firefox 它出现了,我 运行 firefox 整个程序得到 stopped (根据终端的内容至少说)。我不知道为什么会这样。

还是感谢Antti!