【问题标题】:why fork function mess up the file input in my code?为什么 fork 函数会弄乱我代码中的文件输入?
【发布时间】:2021-03-08 07:56:59
【问题描述】:

当我使用 fork() 创建和终止进程时,突然我的输出行为异常。我希望我的程序读取所有行并终止。但是,当它使用 fork 时,while 循环永远不会终止。 这是我的代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>

#define MAXLINE 1024

int main(void) {
  char buf[MAXLINE];
 
  printf("prompt: \n");

  while (fgets(buf, MAXLINE, stdin) != NULL){
  
   if (fprintf(stdout, "PParent: %s\n", buf) == EOF)
          printf("output err");
    
    pid_t child = fork();

    switch(child){
      case -1: 
        perror("fork");
        exit(EXIT_FAILURE);
        break;
      case 0:
        printf("Child %d: dying\n", getpid());
        exit(0);
    
        break;
      default:
    
    waitpid (child, NULL, 0);
    
    }
  }
  exit(0);
}

我希望我的输出是

prompt: 
Parent: THIS IS LINE 1
Child 31201: dying
Parent: THIS IS LINE 2
Child 31202: dying
Parent: THIS IS LINE 3
Child 31203: dying
Parent: THIS IS LINE 4
Child 31204: dying
Parent: THIS IS LINE 5
Child 31205: dying
Parent: THIS IS LINE 6
Child 31206: dying
Parent: THIS IS LINE 7
Child 31207: dying
Parent: THIS IS LINE 8
Child 31208: dying
Parent: THIS IS LINE 9
Child 31209: dying
Parent: THIS IS LINE 10
Child 31210: dying
Parent: THIS IS LINE 11
Child 31211: dying

但我明白了:

prompt: 
Parent: THIS IS LINE 1
Child 14090: dying
Parent: THIS IS LINE 2
Child 14091: dying
Parent: THIS IS LINE 3
Child 14092: dying
Parent: THIS IS LINE 4
Child 14093: dying
Parent: THIS IS LINE 5
Child 14094: dying
Parent: THIS IS LINE 6
Child 14095: dying
Parent: THIS IS LINE 7
Child 14096: dying
Parent: THIS IS LINE 8
Child 14097: dying
Parent: THIS IS LINE 9
Child 14098: dying
Parent: THIS IS LINE 10
Child 14099: dying
Parent: THIS IS LINE 11
Child 14100: dying
Parent: THIS IS LINE 2
Child 14101: dying
Parent: THIS IS LINE 3
Child 14102: dying
Parent: THIS IS LINE 4
Child 14103: dying
Parent: THIS IS LINE 5
Child 14104: dying
Parent: THIS IS LINE 6
Child 14105: dying
Parent: THIS IS LINE 7
Child 14106: dying
Parent: THIS IS LINE 8
Child 14107: dying
Parent: THIS IS LINE 9
Child 14108: dying
Parent: THIS IS LINE 10
Child 14109: dying
Parent: THIS IS LINE 11
Child 14110: dying
Parent: 
Child 14111: dying
Parent: THIS IS LINE 2
Child 14112: dying
Parent: THIS IS LINE 3
...

它会永远存在。如果我不使用 fork,我将获得所需的输出。但我的问题是为什么使用 fork 导致输出是这样的?子进程在创建后立即被杀死,甚至没有使用 STDIN。

【问题讨论】:

  • 您的程序似乎可以运行:它在 fgets() 遇到 EOF 时结束。因此,如果您执行以下操作: cat file | your_program ==> 这将在文件末尾停止。如果您以交互方式进行,CTRL-D 将为 fgets() 生成 EOF 并使程序结束。那么,有什么意义呢?你能分享一个执行的例子吗?
  • 您是否同意每次阅读一行时都会创建一个新的子进程这一事实?孩子在每行阅读后显示“垂死”。可能是您的问题是 while 循环的右括号,因为您可能想在循环之后分叉子项。因此,请查看位于 switch 之后(而不是之前)的 while 循环的右括号。
  • 问题是我希望我的程序表现得像我不分叉任何孩子时一样。但是,考虑 10 行的输入。我希望程序读取所有 10 行并退出(打印孩子死没问题)。但是,我得到了一个输出,比如从第 1 行打印到第 10 行和第 2 行到第 10 行以及空白空间和第 2 到 10 行(永远存在)。我不知道是什么系统调用导致程序出现这种异常行为。我不想修复它,我想知道是什么原因。
  • @b.andarzian:不要评论你自己的问题,edit它来改进它!

标签: c fork stdin fgets waitpid


【解决方案1】:

使用所有警告和调试信息编译您的代码:GCC 表示gcc -Wall -Wextra -g

仔细阅读当然是Advanced Linux Programming然后syscalls(2)fork(2)errno(3)

然后使用GDB调试器(例如它的breakstepbacktraceprintrun命令)。当然,您应该阅读 GCC 和 GDB 的文档

根据经验,由于stdio(3) 被缓冲,每次调用fork(2) 之前都应该调用fflush(3)

while (fgets(buf, MAXLINE, stdin) != NULL){

对于交互式程序,您可以考虑使用GNU readline 而不是fgets。请参阅readline(3)(它提供编辑功能)。或者至少使用getline(3)

我猜你应该编码:

if (fprintf(stdout, "PParent: %s\n", buf) == EOF)
      printf("output err");
fflush(NULL); ////// this was missing
pid_t child = fork();

当然同时使用gdb(1)strace(1) 来了解程序的行为,并了解syscalls(2) 的执行情况。您可能对proc(5) 感兴趣。

顺便说一句,fprintf(stdout, 几乎等同于printf。因此,如果您的fprintf(stdout 失败,则以下printf 很可能也会失败。再仔细阅读printf(3)

但我的问题是为什么使用 fork 导致输出是这样的?

因为父进程和子进程都在运行concurrently。如果你的处理器是multi-core(2020 年很常见),子进程可以在核心 0 上运行,父进程在核心 1 上,Xorg 服务器在核心 2 上,终端模拟器在核心 3 和 所有进程同时运行

另外,Linuxkernel will schedule在每个内核上都有几个进程,使用了一些时间共享。

细节比较复杂,请看好operating system tutorial textbook。您需要阅读数百页!

子进程在创建后立即被杀死,甚至不使用 STDIN。

但它使用(作为父进程)stdout(3) ....

子进程在创建后立即被杀死

您显示的代码没有使用kill(2),所以我们不明白孩子是如何被杀死的。当然signal(7)也是如此。你的孩子是exit(3)-ing,它确实做了一些处理(另见atexit(3)...),可能相当于fflush(NULL)(见fflush(3))。该处理确实需要一些时间。

考虑研究GNU libccrt0 例程的源代码。

在 Linux 上两者都是 open source。 Linux kernel 也是开源的。另请参阅Linux From Scratch,考虑下载、学习、编译您正在使用的所有源代码

【讨论】:

  • 我在这里看不到答案。我询问了程序差异的原因。你可以只运行一次代码吗?那你就知道我在说什么了。
  • 不,我没有要求做作业。我询问了背后的原因。这不是作业,而是好奇心。
  • 要逐行运行代码——实际上是一步一步——使用GDB及其step命令。要了解程序的行为,请使用 strace
  • 是的,我之前做过,但是在 fork 系统调用之后我的缓冲区变得很奇怪。
  • 所以您了解您的错误,不是吗?而且我不明白您所说的“看到我的缓冲区变得奇怪”是什么意思。请在您的问题中解释这一点(不清楚)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-13
  • 1970-01-01
  • 1970-01-01
  • 2020-08-17
  • 1970-01-01
相关资源
最近更新 更多