这是因为 SIGTTOU 是通过在后台进程中尝试 tcsetpgrp 生成的,如手册中所述:
如果从后台进程组对调用者的控制终端调用 tcsetpgrp(),则可能会生成 SIGTTOU 信号,具体取决于进程如何处理 SIGTTOU:
您可以通过运行“strace -f ./a.out”并观察子进程的输出来看到这一点(“-f”表示跟随分叉):
[pid 4062] setpgid(4062, 0) = 0
[pid 4062] write(1, "now setting to 4062\n", 20now setting to 4062
) = 20
[pid 4062] ioctl(1, TIOCSPGRP, [4062]) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid 4062] --- SIGTTOU {si_signo=SIGTTOU, si_code=SI_KERNEL} ---
tcsetpgrp() 被库翻译成ioctl,我们可以看看发生了什么。
在此处复制指向的代码:
/* CELEBT10
*
* This example changes the PGID.
*
* */
#define _POSIX_SOURCE
#include <termios.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <signal.h>
int main() {
pid_t pid;
int status;
if (fork() == 0)
{
// signal(SIGTTOU, SIG_IGN); // UNCOMMENT ME
if ((pid = tcgetpgrp(STDOUT_FILENO)) < 0)
perror("tcgetpgrp() error");
else {
printf("original foreground process group id of stdout was %d\n",
(int) pid);
if (setpgid(getpid(), 0) != 0)
perror("setpgid() error");
else {
printf("now setting to %d\n", (int) getpid());
if (tcsetpgrp(STDOUT_FILENO, getpid()) != 0)
perror("tcsetpgrp() error");
else if ((pid = tcgetpgrp(STDOUT_FILENO)) < 0)
perror("tcgetpgrp() error");
else
printf("new foreground process group id of stdout was %d\n", (int) pid);
fflush(stdout);
}
}
}
else wait(&status);
}
查看注释“取消注释我”,它将允许该功能继续:
$ ./a.out
original foreground process group id of stdout was 4070
now setting to 4071
new foreground process group id of stdout was 4071
自从我不得不这样做已经有很多年了,所以我对基本原理很模糊,但我相信后台进程不应该写入终端并搞乱前台进程正在做的任何事情。很多时候,将自己置于后台的代码会重定向其输入/输出以将自己与前台终端分离。
通过捕获(或忽略)信号,代码可以明确表达其意图,但我不确定随意的“忽略信号”是否会自动成为正确答案;我们需要了解这段代码如何适应更大的图景。