printfexception后“fork()”

操作系统:Linux,语言:纯粹的C

一般情况下,我正在学习C编程,而在UNIX下C编程则是一个特例。

在使用fork()调用之后,我检测到printf()函数的一个奇怪的(对我来说)行为。

 #include <stdio.h> #include <system.h> int main() { int pid; printf( "Hello, my pid is %d", getpid() ); pid = fork(); if( pid == 0 ) { printf( "\nI was forked! :D" ); sleep( 3 ); } else { waitpid( pid, NULL, 0 ); printf( "\n%d was forked!", pid ); } return 0; } 

产量

 Hello, my pid is 1111 I was forked! :DHello, my pid is 1111 2222 was forked! 

为什么在孩子的输出中出现第二个“Hello”string?

是的,这正是母公司开始时打印的父母的pid

但! 如果我们在每个string的末尾放置一个\n字符,我们将得到预期的输出:

 #include <stdio.h> #include <system.h> int main() { int pid; printf( "Hello, my pid is %d\n", getpid() ); // SIC!! pid = fork(); if( pid == 0 ) { printf( "I was forked! :D" ); // removed the '\n', no matter sleep( 3 ); } else { waitpid( pid, NULL, 0 ); printf( "\n%d was forked!", pid ); } return 0; } 

输出

 Hello, my pid is 1111 I was forked! :D 2222 was forked! 

为什么会发生? 这是正确的行为,还是一个错误?

我注意到<system.h>是一个非标准的头文件; 我用<unistd.h>replace它,代码编译干净。

当你的程序输出到terminal(屏幕),它是行缓冲。 当你的程序输出到一个pipe道,它是完全缓冲。 您可以通过标准C函数setvbuf()_IOFBF (完全缓冲), _IOLBF (行缓冲)和_IONBF (无缓冲)模式来控制缓冲模式。

你可以在你的修改程序中演示这个,把你的程序的输出传给cat 。 即使在printf()string末尾有换行符,也会看到双重信息。 如果你把它直接发送到terminal,那么你会看到只是一个很多的信息。

故事的寓意是要小心地叫fflush(0); 在分叉之前清空所有的I / O缓冲区。


逐行分析,按照要求(大括号等删除 – 和标记编辑器删除领先的空间):

  1. printf( "Hello, my pid is %d", getpid() );
  2. pid = fork();
  3. if( pid == 0 )
  4. printf( "\nI was forked! :D" );
  5. sleep( 3 );
  6. else
  7. waitpid( pid, NULL, 0 );
  8. printf( "\n%d was forked!", pid );

分析:

  1. 复制“你好,我的pid是1234”到标准输出的缓冲区中。 由于最后没有换行符,并且输出以线路缓冲模式(或全缓冲模式)运行,因此terminal上不显示任何内容。
  2. 给我们两个独立的进程,在stdout缓冲区中使用完全相同的材料。
  3. 孩子有pid == 0并执行第4行和第5行; 父对于pid有一个非零值(两个进程之间less​​数差异之一 – getpid()getppid()返回值是两个)。
  4. 将一个换行符和“I was forkked!:D”添加到子节点的输出缓冲区中。 输出的第一行出现在terminal上; 由于输出是行缓冲的,其余的都保存在缓冲区中。
  5. 一切都停止3秒。 在此之后,孩子通过主返回结束正常退出。 此时,stdout缓冲区中的残留数据将被刷新。 由于不存在换行符,因此这会将输出位置留在行尾。
  6. 父母来到这里。
  7. 父母等待孩子完成死亡。
  8. 父母添加一个换行符,“1345分叉!” 到输出缓冲区。 换行符会将“Hello”消息刷新到输出,在子节点生成的不完整行之后。

父目录现在通过main的返回正常退出,剩余的数据被刷新; 由于最后还没有换行符,光标位置在感叹号后面,并且shell提示符出现在同一行上。

我所看到的是:

 Osiris-2 JL: ./xx Hello, my pid is 37290 I was forked! :DHello, my pid is 37290 37291 was forked!Osiris-2 JL: Osiris-2 JL: 

PID号码是不同的 – 但整体外观清晰。 在printf()语句的末尾添加换行符(这很快就成为标准操作)会改变输出:

 #include <stdio.h> #include <unistd.h> int main() { int pid; printf( "Hello, my pid is %d\n", getpid() ); pid = fork(); if( pid == 0 ) printf( "I was forked! :D %d\n", getpid() ); else { waitpid( pid, NULL, 0 ); printf( "%d was forked!\n", pid ); } return 0; } 

我现在得到:

 Osiris-2 JL: ./xx Hello, my pid is 37589 I was forked! :D 37590 37590 was forked! Osiris-2 JL: ./xx | cat Hello, my pid is 37594 I was forked! :D 37596 Hello, my pid is 37594 37596 was forked! Osiris-2 JL: 

请注意,输出到terminal时,它是行缓冲的,所以“Hello”行出现在fork()之前,并且只有一个副本。 当输出stream向cat ,它是完全缓冲的,所以在fork()之前没有任何东西出现,并且两个进程都有缓冲区中的“Hello”行被刷新。

之所以这样,是因为如果在格式string末尾没有\n ,那么值不会立即被打印到屏幕上。 而是在stream程中缓冲。 这意味着直到fork操作之后它才会被打印出来,所以你要打印两次。

添加\n虽然强制缓冲区被刷新并输出到屏幕上。 这发生在叉之前,因此只打印一次。

您可以使用fflush方法强制执行此操作。 例如

 printf( "Hello, my pid is %d", getpid() ); fflush(stdout); 

fork()有效地创build了一个进程的副本。 如果在调用fork()之前,它有缓冲的数据,则父级和子级都将具有相同的缓冲数据。 下一次,他们每个人都做了一些冲洗缓冲区(例如在terminal输出的情况下打印一个换行符),除了该进程产生的任何新输出外,您还将看到缓冲输出。 所以,如果你要在父母和孩子中使用stdio,那么你应该在分叉之前进行刷新,以确保没有缓冲数据。

通常,这个孩子只能用来调用exec*函数。 因为这代替了完整的subprocess映像(包括任何缓冲区),所以在技术上没有必要fflush如果这真的是你要在孩子身上所做的一切。 但是,如果可能存在缓冲的数据,则应该小心如何处理exec失败。 特别是,避免使用任何stdio函数( write is ok)将错误打印到stdout或stderr,然后调用_exit (或_Exit )而不是调用exit或只是返回(这将刷新任何缓冲的输出)。 或者在分叉之前通过冲洗来完全避免这个问题。