linux gcc中fork()的工作[重复]

Posted

技术标签:

【中文标题】linux gcc中fork()的工作[重复]【英文标题】:Working of fork() in linux gcc [duplicate] 【发布时间】:2011-03-31 14:32:22 【问题描述】:

fork()创建一个新进程,子进程从父进程的当前状态开始执行。

这是我对 Linux 中 fork() 的了解。

所以,相应的代码如下:

int main() 
  printf("Hi");
  fork();
  return 0;

只需要按上述方式打印一次“Hi”。

但是在使用 gcc 编译的 Linux 中执行上述操作时,它会打印“Hi”两次

有人可以向我解释一下使用 fork() 时实际发生的情况以及我是否正确理解了 fork() 的工作原理吗?

【问题讨论】:

如果你在你的 fork 之前添加一个fflush(stdout); 那么它会做你期望它做的事情。 【参考方案1】:

(结合用户@Jack 的评论中的一些解释) 当您将某些内容打印到“标准输出”标准输出(通常是计算机显示器,尽管您可以将其重定向到文件)时,它最初会存储在临时缓冲区中。

fork 的两边都继承了 unflushed 缓冲区,所以当 fork 的每一边都命中 return 语句并结束时,它会被刷新两次。

在你 fork 之前,你应该 fflush(stdout); 刷新缓冲区,这样子进程就不会继承它。

到屏幕的stdout(与将其重定向到文件时相反)实际上是由行尾缓冲的,所以如果你完成了printf("Hi\n");,你就不会遇到这个问题,因为它会被刷新缓冲区本身。

【讨论】:

先生,您能否详细解释一下您的答案。我是在 gcc 中做 c 程序的新手。所以我无法理解你的答案!! 你应该可以通过fflush(stdin)来测试这个的准确性 @Shyam:当您将某些内容打印到 STDOUT(通常是计算机显示器)时,它最初会存储在临时缓冲区中。当您进行分叉时,该缓冲区由孩子继承。当缓冲区被刷新时,您可以从两个进程中看到它。如果您手动使用 fflush,缓冲区将被清除,并且子进程不会继承该缓冲区。然后你只会看到一张打印。 @Jack,这是一个很好的解释。您介意我将其包含在我的答案中吗? 如果标准输出是一个普通文件,它通常会被块缓冲。不要将标准输出与 tty 混为一谈。【参考方案2】:

printf("Hi"); 实际上并不会立即将“Hi”这个词打印到您的屏幕上。它所做的是用单词“Hi”填充stdout 缓冲区,然后在缓冲区“刷新”后显示。在这种情况下,stdout 指向您的显示器(假设)。在这种情况下,当缓冲区已满、强制刷新或(最常见)打印换行符(“\n”)时,缓冲区将被刷新。由于在调用fork() 时缓冲区仍然是满的,父进程和子进程都会继承它,因此它们在刷新缓冲区时都会打印出“Hi”。如果你在调用 fork 之前调用 fflush(stout);,它应该可以工作:

int main() 
  printf("Hi");
  fflush(stdout);
  fork();
  return 0;

或者,正如我所说,如果您在 printf 中包含换行符,它也应该可以工作:

int main() 
  printf("Hi\n");
  fork();
  return 0;

【讨论】:

C 中比我更有经验的人可以确认输入换行符时标准输出缓冲区会刷新吗?我从某个地方获得了这些知识,但我不是 100% 确定。 C 知道 3 种缓冲模式:unbufferedfully bufferedline buffered。来自 C99 §7.19.3 (7):“[...] 最初打开时,标准错误流没有完全缓冲;标准输入和标准输出流被完全缓冲当且仅当流可以确定不指的是交互式设备。”【参考方案3】:

一般来说,在 fork() 的任何一侧都有库正在使用的打开句柄/对象是非常不安全的。

这包括 C 标准库。

fork() 将两个进程合二为一,没有库可以检测到它的发生。因此,如果两个进程继续使用相同的文件描述符/套接字等运行,它们现在具有不同的状态但共享相同的文件句柄(从技术上讲,它们具有副本,但具有相同的底层文件)。这会让坏事发生。

fork() 导致此问题的示例

stdio 例如tty 输入/输出、管道、磁盘文件 例如使用的套接字数据库客户端库 服务器进程使用的套接字 - 当服务一个套接字的子进程碰巧继承了另一个文件句柄时,可能会产生奇怪的效果 - 正确进行这种编程是很棘手的,请参阅 Apache 的源代码以获取示例。李>

一般情况下如何解决这个问题:

要么

a) 在 fork() 之后立即调用 exec(),可能在同一个二进制文件上(使用必要的参数来完成您打算做的任何工作)。这很容易。

b) 分叉后,不要使用任何现有的打开句柄或依赖它们的库对象(打开新句柄是可以的);尽快完成工作,然后调用 _exit() (而不是 exit() )。不要从调用 fork 的子例程返回,因为这可能会调用 C++ 析构函数等,这可能会对父进程的文件描述符造成不良影响。这很容易。

c) 在 fork 之后,以某种方式清除所有对象并使它们都处于理智状态,然后再让孩子继续。例如关闭底层文件描述符而不刷新缓冲区中的数据,该缓冲区在父项中重复。这很棘手。

c) 大约是 Apache 所做的。

【讨论】:

【参考方案4】:

printf() 进行缓冲。你试过打印到stderr吗?

【讨论】:

缓冲是流的默认属性。您可以关闭缓冲...cerr 在这种情况下称为fprintf(stderr)【参考方案5】:

技术答案:

当使用 fork() 时,您需要确保 exit() 不会被调用两次(从 main 中脱落与调用 exit() 相同)。孩子(或者很少是父母)需要调用 _exit 来代替。另外,不要在孩子中使用 stdio。那只是自找麻烦。

有些库有一个 fflushall(),你可以在 fork() 之前调用它,这使得孩子中的 stdio 是安全的。在这种特殊情况下,它也会使 exit() 安全,但在一般情况下并非如此。

【讨论】:

以上是关于linux gcc中fork()的工作[重复]的主要内容,如果未能解决你的问题,请参考以下文章

我的 linux 的 gcc 编译器不支持布尔值 [重复]

内核状态如何在fork上重复?

使用printf在c中fork()[重复]

在 fork 中更新主分支 [重复]

为啥 fork() 两次[重复]

如何在 github 上更新 fork [重复]