在linux gcc中使用fork()

时间:2010-08-18 14:39:04

标签: c linux gcc fork

  

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

这是我在Linux中了解fork()的事情。

因此,相应的代码如下:

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

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

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

有人可以向我解释使用fork()时实际发生了什么,以及我是否正确了解了fork()的工作情况?

5 个答案:

答案 0 :(得分:36)

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

fork的两端都继承了未刷新的缓冲区,因此当fork的每一侧都命中return语句并结束时,它会被刷新两次。

在你分叉之前,你应该fflush(stdout);来刷新缓冲区,以便孩子不会继承它。

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

答案 1 :(得分:22)

printf("Hi");实际上并不会立即在屏幕上打印“Hi”字样。它所做的是在stdout缓冲区填充单词“Hi”,然后在缓冲区被“刷新”后显示。在这种情况下,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;
}

答案 2 :(得分:8)

通常,fork()两侧的库使用打开的句柄/对象是非常不安全的。

这包括C标准库。

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

fork()导致此问题的示例

  • stdio例如tty输入/输出,管道,光盘文件
  • 例如,使用的插座数据库客户端库
  • 服务器进程正在使用的套接字 - 当一个子服务器为一个套接字提供服务时,它可能会产生奇怪的效果 - 为了获得这样的编程是正确的,请参阅Apache的源代码示例。

如何解决一般情况:

无论

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

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

c)分叉后,以某种方式清理所有物体,并让它们在孩子继续之前处于理智状态。例如关闭基础文件描述符而不刷新在父级中复制的缓冲区中的数据。这很棘手。

c)与Apache的相似。

答案 3 :(得分:3)

printf()做缓冲。您是否尝试过打印到stderr

答案 4 :(得分:2)

技术答案:

当使用fork()时,你需要确保exit()没有被调用两次(从main掉下来与调用exit()相同)。孩子(或很少是父母)需要调用_exit。另外,不要在孩子身上使用stdio。那只是在寻找麻烦。

有些库有一个fflushall(),你可以在fork()之前调用它,使子进程中的stdio安全。在这种特殊情况下,它也会使exit()安全,但在一般情况下则不然。