【问题标题】:How to only kill the child process in the foreground?如何只杀死前台的子进程?
【发布时间】:2018-05-10 19:49:08
【问题描述】:

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

【问题讨论】:

    标签: c fork signal-handling sigaction


    【解决方案1】:

    这很简单,但不是用信号来完成的。相反,您必须使用称为进程组的功能。每个单独的job(可执行或管道等)都将是一个单独的进程组。您可以使用setpgid(或在某些系统上使用setpgrp)创建进程组。可以简单的在fork之后exec之前设置子进程的进程组,然后把这个job的进程组id存入job表中。

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

    【讨论】:

    • 感谢您告诉我有关进程组的信息!但是,我不知道如何将setpgidtcsetpgrp 合并到我的代码中。比如我不知道怎么把东西存入job表,或者tcsetpgrp的参数怎么用。能给我一个简短的例子吗?
    • 哈哈我自己还没试过
    • 哎呀,我正在研究如何使用它们,看起来很奇怪
    • 我找到了解决问题的方法,我需要做的就是添加一行setpgid!谢谢!
    【解决方案2】:

    在 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。这意味着在上面的行中,我将进程的 pgid 设置为 pid pidpid

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

    至少这是我能收集到的。我仍然不完全理解tcsetpgrp,尤其是我可以设置为第一个参数的内容,即文件描述符。在setpgid 之后添加这一行:

    tcsetpgrp(STDIN_FILENO, pid)

    每当我exec 一个命令时,只需在后台启动整个程序。我没有运行firefox 并显示它,而是运行firefox 并且整个程序得到stopped (至少根据终端所说的)。我不知道为什么会这样。

    仍然感谢 Antti!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-04-13
      • 1970-01-01
      • 1970-01-01
      • 2010-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多