c - 只有当所有 children 都终止时,waitpid() 和 co 是否检测到 child 终止?

c - does waitpid() and co detect child termination only when all children are terminated?

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

所以我一直在研究多个进程如何访问单个文件。我有一个使 fork 两次的基本代码 - 两次 children fprintf 进入同一个文件,主要区别在于其中一个 sleeps 并且做更多fprintf秒。 Child exit 当所有 fprinf 都完成时。同时 parent waitpids,每次 child 终止它 fprintfs 进入同一个文件。

我想看到的是 1) child 进程有更多 fprintfs 和 sleep 比另一个 child 更晚被终止(我认为差异运行 时间应该提供了一个很好的发生这种情况的可能性)——而且确实发生了; 2) 在文件中间某处查看 parent 进程的第一个 fprintf 作为(我是怎么想的!)第一个 child 应该在 waitpid 之前编辑第二个被终止了——事实并非如此。

每次发生的情况是 fprintf 都被文件末尾的 parent 文件生成到文件中,就像 parent 等待两个children 被终止,然后才 waitpided 他们。

wait 交换 waitpid 显然产生相同的结果。

我有几个猜测:

  1. 第二个 child 终止速度比 parent 有时间 waitpid 第一个 fprintf 进入文件。
  2. OS 没有时间在第二个 child 终止之前将 SIGCHILD 发送到 parent。
  3. 这就是 waitpid 的工作方式,比如信号是否排队? (但我还没有找到任何此类功能的规范)。

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


程序代码:

  #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!

不,waitpid 会在每次 child 退出时执行 return。问题是你的测试有缺陷。

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

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

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