为什么 STDIN 没有传播到不同进程组的 child 个进程?
Why is STDIN not propagated to child process of different process group?
下面是执行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(sh
或 bash
)中时,我预计:
- parent 生成
cat
的 child 进程,并且
- child 开始与终端交互。
然而,发生的事情是:
- parent 生成
cat
没问题,但是
- child已停止(进程状态代码
T
在ps
),并且
- child 不接受来自终端的输入,
- child不回应
SIGINT
、SIGSTOP
或SIGQUIT
中的任何一个,只会被SIGKILL
杀死。
当对 setpgid()
的调用被注释掉时,一切都如预期的那样。
我怀疑该行为是由以下原因引起的:
- child
cat
正在尝试读取标准输入并正在等待(因此停止),但是
- 终端的输入首先传递给
bash
,然后传递给上面的程序,但是没有传递给cat
,可能是因为bash
不认识它的grandchildren,cat
,因为它的进程组不同。
当然,删除setpgid()
调用是最简单的解决方案。不幸的是有一些原因;主要是拦截parent中的一些信号(比如SIGINT或者SIGSTOP)。换句话说,<Ctrl-C>
不应该杀死 cat
而是以某种方式向上面的程序发出信号。 (上面的程序中没有信号处理程序,是的,用于说明目的。)
我想问:
- 这是正确的吗?
- 不管是不是,我怎样才能
cat
接收标准输入的输入?
如评论中所建议,可以通过 tcsetpgrp()
.
更改前台进程组(假定所有标准输入)
该函数也可以从 child 中调用。否则 parent 将不得不等待 child 执行成功的 setpgid()
调用并且会发生并发问题。
然而,正如SO question中所描述的,当child(还不是前台)调用tcsetpgrp
时,它会得到一个SIGTTOU信号,根据手册tcsetpgrp
个。 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
下面是执行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(sh
或 bash
)中时,我预计:
- parent 生成
cat
的 child 进程,并且 - child 开始与终端交互。
然而,发生的事情是:
- parent 生成
cat
没问题,但是 - child已停止(进程状态代码
T
在ps
),并且 - child 不接受来自终端的输入,
- child不回应
SIGINT
、SIGSTOP
或SIGQUIT
中的任何一个,只会被SIGKILL
杀死。
当对 setpgid()
的调用被注释掉时,一切都如预期的那样。
我怀疑该行为是由以下原因引起的:
- child
cat
正在尝试读取标准输入并正在等待(因此停止),但是 - 终端的输入首先传递给
bash
,然后传递给上面的程序,但是没有传递给cat
,可能是因为bash
不认识它的grandchildren,cat
,因为它的进程组不同。
当然,删除setpgid()
调用是最简单的解决方案。不幸的是有一些原因;主要是拦截parent中的一些信号(比如SIGINT或者SIGSTOP)。换句话说,<Ctrl-C>
不应该杀死 cat
而是以某种方式向上面的程序发出信号。 (上面的程序中没有信号处理程序,是的,用于说明目的。)
我想问:
- 这是正确的吗?
- 不管是不是,我怎样才能
cat
接收标准输入的输入?
如评论中所建议,可以通过 tcsetpgrp()
.
该函数也可以从 child 中调用。否则 parent 将不得不等待 child 执行成功的 setpgid()
调用并且会发生并发问题。
然而,正如SO question中所描述的,当child(还不是前台)调用tcsetpgrp
时,它会得到一个SIGTTOU信号,根据手册tcsetpgrp
个。 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