为什么fflush()会影响fork子进程的输出?

3

我正在尝试学习UNIX编程,并遇到了一个关于fork()的问题,我无法解释下面两个程序的输出。

我知道fork()会创建一个与当前运行进程完全相同的进程,但它从哪里开始呢?例如,如果我有以下这两个程序,它们的输出将是什么,它是如何工作的?

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

int main (int argc, char **argv)
{
    int retval;
    printf ("This is most definitely the parent process\n");
    // now here fork will create a child process 
    // i need to know from which line child process starts execution 

    retval = fork ();
    printf ("Which process printed this?\n");

    return (0);
}

关于子进程执行,上面的程序和下面的程序有何不同:

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

int main (int argc, char **argv)
{
    int retval;
    printf ("This is most definitely the parent process\n");

    fflush (stdout);
    // how does fflush change the output of above program and why ?
    // even though no string operations are being used
    retval = fork ();
    printf ("Which process printed this?\n");

    return (0);
 }

我认为他们都应该打印:

This is most definitely the parent process 
Which process printed this? 
Which process printed this? 

但是第一个问题是打印:
This is most definitely the parent process 
Which process printed this? 
This is most definitely the parent process 
Which process printed this? 

1
为什么不试一下并告诉我们输出的结果呢? - Sourav Ghosh
我已经编辑了帖子。感谢您的建议! - Rajeswar Rao
你尝试过阅读fork(2)的man手册吗? - SirPython
3个回答

6
我知道fork()会创建一个当前运行进程的相同进程,但它从哪里开始呢?
如果fork(2)成功(即不返回-1),它将从调用fork(2)的行开始。fork(2)会返回两次:在子进程中返回0,在父进程中返回正数C,其中C是新生子进程的进程ID。
您看到"This is most definitely the parent process"两次的原因与stdio的缓冲有关。stdio会使用用户空间缓冲区缓冲输出,只有在某些情况下(例如,缓冲区变满),这些缓冲区才会被刷新。缓冲模式规定何时以及如何刷新缓冲区。
通常,如果输出被写入交互设备,例如终端(或伪终端),stdio会使用行缓冲,这意味着当发现换行符或调用fflush(3)时,缓冲区将被刷新。
另一方面,如果输出被重定向到文件或其他非交互设备(例如,输出被重定向到管道),则stdio会使用全缓冲,这意味着只有在缓冲区变满或调用fflush(3)时才会刷新缓冲区。
因此,在没有调用fflush(3)的情况下,在终端设备中执行代码会打印出这个:
This is most definitely the parent process
Which process printed this?
Which process printed this?

这是可以预料的。然而,如果你通过cat(1)来传输它,你会看到这个(或其他一些变体,取决于执行顺序):

This is most definitely the parent process
Which process printed this?
This is most definitely the parent process
Which process printed this?

这是因为输出被重定向到管道时会完全缓冲。 字符串This is most definitely the parent process不足以填充和刷新缓冲区,因此当父进程fork时,子进程(获得父进程的内存空间副本)将获取输出缓冲区的副本,其中已经包含字符串This is most definitely the parent process。 因此,两个进程最终都打印出该字符串。
如果每次fork之前始终调用fflush(3),则不会发生这种情况,因为当将父进程的内存空间复制到子进程时,缓冲区为空。

0

执行将在 fork 调用之后(或之后不久)继续进行。您可以使用返回值来检查您是父进程还是子进程:

返回值
成功时,在父进程中返回子进程的 PID,在子进程中返回 0。失败时,在父进程中返回 -1,不创建子进程,并适当设置 errno。

(来源:man fork

例如,如果您有以下程序:

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

int main(int argc, char **argv) {
    printf("Foo.\n");
    int retval = fork();
    printf("Bar from %s (%d).\n", (retval == 0) ? "child" : "parent", retval);
    return 0;
}

输出结果将会是这样的:

Foo.
Bar from parent (18464).
Bar from child (0).

假设输出是行缓冲的。


fflush如何改变这两个程序的输出? - Rajeswar Rao
如果您的输出是行缓冲的话,它不应该出现这种情况。如果您不确定它是否为行缓冲,请随时调用fflush以确保安全。详情请参见https://dev59.com/FHE85IYBdhLWcg3w9odJ。 - Wander Nauta
我不知道什么是行缓冲,Ubuntu中是否启用了行缓冲? - Rajeswar Rao
你的终端可能使用行缓冲。如果你将程序的输出导入到另一个程序中,它可能是完全缓冲而不是行缓冲。如果你想了解更多,请阅读man setbuf,否则只需添加fflush(0)并忘记它。 - Wander Nauta

0

调用 fork() 时有三种可能的返回条件。

请阅读 fork() 的 man 手册。

这三种返回条件是:

-1 -- the fork() failed
 0 -- the child is executing
some positive number -- the pid of the child, the parent is executing

代码需要像这样:
pid_t pid;

pid = fork();
if ( 0 > pid ) 
{ // then handle error
}
else if ( 0 == pid )
{ // then child executing
}
else // if ( 0 < pid )
{ // then parent executing
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接