【问题标题】:c - does waitpid() and co detect child termination only when all children are terminated?c - 只有当所有孩子都被终止时,waitpid() 和 co 才会检测到孩子的终止?
【发布时间】:2021-03-21 06:10:27
【问题描述】:

我在写这个问题时有点困惑,因为我觉得我遗漏了一些要点(这就是我写它的原因,毕竟)。

所以我一直在研究多个进程如何访问单个文件。我有一个基本代码,使fork 两次 - 两个孩子fprintf 进入同一个文件,主要区别在于其中一个sleeps 和更多fprintfs。当所有fprinfs 完成时,孩子exits。同时父waitpids,每次子终止它fprintfs到同一个文件中。

我想看到的是 1) 有更多 fprintfs 和 sleep 的子进程在另一个子进程之后被终止(我认为运行时间的差异应该会提供一个很好的可能性)-那会发生; 2)在文件中间某处看到父进程的第一个fprintf(我怎么想!)在第二个终止之前,第一个孩子应该是waitpided - 这不是发生的事情。

每次发生的情况是两个fprintfs 都由文件末尾的父文件产生到文件中,就像父文件等到两个孩子都被终止然后才waitpided 他们一样。

waitpidwait 交换显然会产生相同的结果。

我有几个猜测:

  1. 第二个孩子终止得更快,然后父母有时间将waitpid 第一个孩子和fprintf 放入文件中。
  2. 在第二个子节点终止之前,操作系统没有时间将SIGCHILD 发送给父节点。
  3. 这就是waitpid 的工作方式,比如信号是否已排队? (但我还没有找到任何此类功能的规范)。

有人能解释一下为什么我没有收到关于第一个孩子在文件中间终止的消息,而是在最后得到它吗?


程序代码:

  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <errno.h>
  #include <sys/wait.h>
  
  #define N 1000000
  #define SLEEP_TIME 20
  
  int main(void)
  {
      FILE *fd1 = fopen("test.txt", "w");
      pid_t pid1, pid2, cpid;
      int wstatus;
  
      pid1 = fork();
      if(0 == pid1) {
          for(int i = 0; i < N; ++i) {
              fprintf(fd1, "child1 %d %d\n", getpid(), i);
          }
          sleep(SLEEP_TIME);
          for(int i = 0; i < N; ++i) {
              fprintf(fd1, "child1a %d %d\n", getpid(), i);
          }
          sleep(SLEEP_TIME);
          fclose(fd1);
          exit(EXIT_SUCCESS);
      } 
      else if(-1 == pid1) {
          exit(EXIT_FAILURE);
      }
      
      pid2 = fork();
      if(0 == pid2) { 
          for(int i = 0; i < N/2; ++i) {
              fprintf(fd1, "child2 %d %d\n", getpid(), i);
          }
          fclose(fd1);
          exit(EXIT_SUCCESS);
      } 
      else if(-1 == pid2) {
          exit(EXIT_FAILURE);
      }
      
      while(((cpid = wait(&wstatus)) != -1)) {
      //while(((cpid = waitpid(-1, &wstatus, WUNTRACED | WCONTINUED)) != -1))               if(WIFEXITED(wstatus))
              fprintf(fd1, "child %d exited with status %d\n", cpid, wstatus);
      }        
      if(errno == ECHILD) { 
          fprintf(fd1, "All children are waited for!\n");
      }
      else {
          perror("waitpid");
          exit(EXIT_FAILURE);
      }
      fclose(fd1);
      
      exit(EXIT_SUCCESS);
  }

结果文件的最后几行:

2499998 child1a 7359 999997
2499999 child1a 7359 999998
2500000 child1a 7359 999999
2500001 child 7360 exited with status 0 //I wanted this one to be in the middle of the file!
2500002 child 7359 exited with status 0
2500003 All children are waited for!

【问题讨论】:

  • 请不要在您的minimal reproducible example 中包含行号,这会使我们更难尝试并复制和调试您的问题。如果您想在源代码中标出特殊行,请在该行添加注释,并在问题的主体中提及。
  • 至于您的问题,wait 系列函数通常归结为内核本身,并且不依赖于用户空间信号。内核拥有关于进程的所有信息,运行、睡眠、等待和退出但未收获。
  • @Someprogrammerdude,知道了,谢谢,枚举已删除。

标签: c fork waitpid


【解决方案1】:

不,每次孩子退出时,waitpid 都会返回。问题是你的测试有缺陷。

在 Unix 上,当您使用 fprintf 等 stdio 函数访问常规文件时,默认情况下它们是 fully buffered。这在只有一个进程正在写入文件时是可取的,因为它减少了系统调用开销,但在时间很重要或尝试与其他进程同步时可能是不可取的。

所以waitpid实际上是在child2退出后立即返回,并且当时正在调用fprintf,但它不会立即将其消息写入文件;相反,它仍然缓冲在父级的内存中。只有当缓冲区填满时(不会发生在父级中,通常是很多 KB),或者当您调用 fflush(您没有),或者当文件关闭时(包括在进程中),它才会被写出出口)。因此,当您在父级中调用 fclose(fd1) 时,两条消息会一起写出,此时两个子级都已退出。

为了更好地说明正在发生的事情的测试,请在打开文件后立即调用 setvbuf(fd1, NULL, _IONBF, 0) 之类的东西来禁用此文件的缓冲。然后您应该会在文件中间看到“child2 exited”消息,正如您所期望的那样。

【讨论】:

  • 当然!谢谢,现在一切都说得通了。
猜你喜欢
  • 1970-01-01
  • 2012-11-04
  • 2023-03-29
  • 2013-02-27
  • 2014-12-09
  • 1970-01-01
  • 2011-01-21
  • 1970-01-01
  • 2013-12-25
相关资源
最近更新 更多