子进程在父进程退出后无法读取

4

使用execvp()函数分叉并执行子进程后,父进程退出。然而这会导致子进程中的fgets()函数立即返回,而不等待从stdin输入。

我猜测父进程的退出会向子进程发送一些信号,导致fgets()函数返回。有人能为我解释更多吗?

子进程的代码:

/* cc child.c -o child */

int main () {
  char buffer[10];
  fgets(buffer, 10, stdin);
  printf("This is what child program read:\n%s", buffer);
}

母程序代码:

/* cc parent.c -o parent */

int main (int argc, char **argv) {
  pid_t pid = fork();
  if (pid < 0) {
    perror("fork");
    exit(EXIT_FAILURE);
  }
  else if (pid == 0) {
    execvp(*(argv+1), argv+1);
  }
  else {
  //    while(1);  if while(1) or wait() is used, child process can wait for input
    exit(1);
  }
}

在zsh shell中:
zsh>: ./parent ./child
zsh>: This is what child program read:   // read nothing and print nothing
2个回答

4
终端由前台进程组控制。当shell调用父进程时,它将父进程设为前台进程组的领导者。子进程继承该组并可以访问终端。
然而,当父进程退出时,shell将重新控制终端并成为前台进程组的领导者。子进程不再是前台进程组的一员,因此无法访问终端。在这种情况下,fgets()将返回NULL,并设置错误代码。
如果您从其他地方(如管道)获取输入,则会发现程序按预期工作。例如:
$ echo test | ./parent ./child

因此,只有当输入来自终端时才会出现此问题。
回顾一下,如果检查了fgets的错误代码,则回答这个问题将更加简单。在这种情况下,fgets返回null,但您需要检查feof()和/或ferror()以确定这是否意味着已到达文件结尾(stdin关闭)或发生了错误。在这种情况下,NULL表示存在EIO错误。
早期错误的答案(请参见评论线索的说明,因为讨论很长,所以将其留在这里):当您进行fork时,子进程会继承stdin等。当父进程退出时,它会关闭stdin,因此子进程尝试从关闭的描述符中读取并获取不到任何内容。通过添加对wait()的调用,您可以保持stdin打开状态,这允许您的子程序按预期工作。

1
这听起来很有道理,但我认为它是错误的(尽管孩子似乎确实获得了EOF)。文件描述符对于每个进程都是私有的,但它们共享一个打开的文件描述。然而,当进程退出时,它的文件描述符被关闭,但POSIX说:_当与打开文件描述相关联的所有文件描述符都已关闭时,将释放打开的文件描述。_父进程退出不应该影响子进程中的文件描述符(它们应该保持打开状态)。 - Jonathan Leffler
1
@nitish712 很好的问题。子进程实际上不会终止,直到父进程关闭了子进程的进程描述符。由于这里的代码没有显式地关闭描述符,这意味着子进程直到父进程退出才会关闭(换句话说,子进程在父进程退出之前不会退出)。您应该能够使用 ps 来检查这一点。 - jdigital
@jdigital 你是对的。"ps -a" 显示出子进程的 Z 状态,这意味着子进程是一个“僵尸”进程,已经终止但未被父进程回收。 - Nmzzz
如果我在子进程正在睡眠时让父进程退出,那么子进程将从 STDIN 文件描述符中读取一些奇怪的字符,这意味着父进程的退出确实会影响到子进程中的文件描述符。 - Nmzzz
@JonathanLeffler 如果父进程和子进程写入同一个文件而不是读取,一切都正常(前提是父进程先退出并关闭文件描述符)。看起来我们看到的奇怪现象与read()函数有关。 - Nmzzz
显示剩余13条评论

0

在打印缓冲区之前,您应该检查fgets()的返回值。检查fgets是否返回NULL,然后才打印缓冲区。


是的,在打印缓冲区之前,我应该检查fgets()的返回值,但我不明白为什么它会返回NULL。@jdigital已经给出了答案。 - Nmzzz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接