为什么STDIN不会传播到不同进程组的子进程?

时间:2019-11-18 15:40:27

标签: c linux stdin

以下是执行cat的程序的来源:

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

int main()
{
  pid_t pid = fork();
  if (!pid) {
    // create a new process group
    setpgid(0, 0);

    execlp("cat", "cat", NULL);
  }

  pid_t reaped = wait(NULL);
  printf("reaped PID = %d\n", (int) reaped);
  return 0;
}

注意对setpgid(0, 0)的呼叫。

在shell(shbash)中运行时,我期望:

  1. 父级产生cat的子级进程,并且
  2. 孩子开始与终端互动。

但是,发生的事情是:

  1. 父级产生cat没问题,但是
  2. 该子对象已被终止(在T中具有进程状态代码ps),并且
  3. 孩子不接受终端机的任何输入,并且
  4. 孩子对SIGINTSIGSTOPSIGQUIT中的任何一个都没有反应,只是被SIGKILL杀死。

当对setpgid()的呼叫被注释掉时,一切都按预期进行。

我怀疑该行为是由以下原因引起的:

  • 孩子cat试图读取stdin并正在等待(因此已停止),但是
  • 对终端的输入首先传递给bash,然后传递给上面的程序,但是不传递给cat,这可能是因为bash无法识别其子孙{{ 1}},因为它的处理组不同。

当然,删除cat调用是最简单的解决方案。不幸的是,有一些原因。主要是为了拦截父级中的某些信号(例如SIGINT或SIGSTOP)。换句话说,setpgid()不应杀死<Ctrl-C>,而应以某种方式向上面的程序发出信号。 (上面的程序中没有信号处理程序,是的,出于说明目的。)

我想问:

  1. 这正确吗?
  2. 无论是否,我怎么有cat接收来自stdin的输入?

1 个答案:

答案 0 :(得分:1)

如评论中所建议,可以通过tcsetpgrp()更改前台进程组(假定所有STDIN)。

该功能也可以从子级调用。否则,父级将不得不等待子级成功进行setpgid()调用,并且会发生并发问题。

但是,根据tcsetpgrp的手册,如本SO question中所述,当孩子(尚未到前台)调用tcsetpgrp时,它将收到SIGTTOU信号。 。 SIGTTOU的默认操作是停止该过程,应该手动将其忽略。

#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>

int main()
{
  // ignore SIGTTOU
  signal(SIGTTOU, SIG_IGN);

  pid_t pid = fork();
  if (!pid) {
    // create a new process group
    setpgid(0, 0);
    tcsetpgrp(STDIN_FILENO, getpgid(0));

    execlp("cat", "cat", NULL);
  }

  pid_t reaped = wait(NULL);
  printf("reaped PID = %d\n", (int) reaped);
  return 0;
}

现在底层的cat开始与终端进行交互,问题得以解决。

uuu@hhh:~$ ./a
sajkfla
sajkfla
wjkelfaw
wjkelfaw
reaped PID = 774
uuu@hhh:~$ ./a