关于fork和printf / write

可能重复:
fork()和输出

通过运行:

#include int main() { fork(); printf("b"); if (fork() == 0) { write(1, "a", 1); }else{ write(1, "c", 1); } return 0; } 

我有cbcabbab ,有人可以向我解释输出吗? 如果可能的话,是否有工具可以逐步查看运行程序?

尝试再次运行它,你可能会获得不同的输出。

至于一步一步查看过程的工具,我认为strace -f可能会有所帮助:

 $ strace -f ./weirdfork execve("./weirdfork", ["./weirdfork"], [/* 35 vars */]) = 0 ... uninteresting boiler plate removed ... clone(Process 8581 attached child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8581 [pid 8580] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 [pid 8581] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 [pid 8580] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0  [pid 8581] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0  [pid 8580] <... mmap resumed> ) = 0x7fe1c7d22000 [pid 8581] <... mmap resumed> ) = 0x7fe1c7d22000 [pid 8581] clone(  [pid 8580] clone(Process 8582 attached  [pid 8581] <... clone resumed> child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8582 Process 8583 attached [pid 8580] <... clone resumed> child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8583 [pid 8580] write(1, "c", 1  [pid 8581] write(1, "c", 1cc) = 1 [pid 8580] <... write resumed> ) = 1 [pid 8581] write(1, "b", 1b  [pid 8580] write(1, "b", 1  [pid 8581] <... write resumed> ) = 1 b[pid 8581] exit_group(0) = ? Process 8581 detached [pid 8580] <... write resumed> ) = 1 [pid 8580] exit_group(0) = ? [pid 8583] write(1, "a", 1  [pid 8582] write(1, "a", 1a) = 1 a[pid 8582] write(1, "b", 1  [pid 8583] <... write resumed> ) = 1 [pid 8583] write(1, "b", 1b) = 1 [pid 8583] exit_group(0) = ? Process 8583 detached b<... write resumed> ) = 1 exit_group(0) = ? Process 8582 detached 

简短回答:不要混用缓冲和无缓冲的代码。

答案很简单:让我们使用以下命令测试源代码的变体:

 $ rm dump $ for X in 0 1 2 3 4 5 6 7 8 9; do for Y in 0 1 2 3 4 5 6 7 8 9; do for Z in 0 1 2 3 4 5 6 7 8 9; do echo `./program` >> dump; done; done; done $ sort -u dump 

这会执行program一千次,并列出它返回的所有唯一输出。

缓冲版本:用fwrite (或printf )替换write

 #include  #include  int main() { fork(); printf("b"); if (fork() == 0) { fwrite("a", 1, 1, stdout); }else{ fwrite("c", 1, 1, stdout); } return 0; } 

这给出了非常规则的输出模式。 实际上,只有六种输出是可能的:

 bababcbc babcbabc babcbcba bcbababc bcbabcba bcbcbaba 

到底是怎么回事?

  1. 在第一个fork之后,有两个进程,W和Y.
  2. 两个进程都向stdout流写了一个字母"b" 。 默认情况下,流是缓冲的,所以。
  3. 在第二个fork之后,有四个进程:W和X,Y和Z.W和X的stdout流具有相同的状态,因此同样的缓冲区仅包含"b" 。 Y和Z也是如此。
  4. 所有四个进程都将另一个字母写入stdout流。
  5. main返回后,C运行时接管。 每个进程刷新缓冲区,包括stdout的缓冲区。

无缓冲版本:通过write替换printf

 #include  int main() { fork(); write(1, "b", 1); if (fork() == 0) { write(1, "a", 1); }else{ write(1, "c", 1); } return 0; } 

现在可能的输出变化多了,但考虑到并发性,它仍然是可以理解的:

 bbacca bbcaac bbcaca bbccaa bcabca bcbaca 

这可能是您期望的输出。

混合版(你的)

您的代码比前两个变体提供了更多的结果:

 cabbacbb cabbcabb cabbcbab cabcabbb cabcbabb cabcbbab ... etc ... 

这是因为write调用会立即产生输出,但缓冲的"b"只会在每个进程终止时打印,当然也就是 write调用之后 。 就像在完全缓冲版本中一样,每个进程在stdout缓冲区中都会有"b" ,所以你最终会看到其中的四个。

除非您专门添加代码来同步分叉进程,否则它们将完全独立运行,因此输出顺序完全是“随机”的。 进程调度将决定接下来要运行的是谁,这又取决于系统中有多少处理器核,运​​行的是什么,以及当前运行的每个进程运行了多长时间。

如链接中所述,您还可以从printf的内部缓冲区获得输出,因为输出尚未写入表示stdout的实际文件中 – 您可以通过添加fflush(stdout);来“修复”该fflush(stdout);printf之后。