fork()在linux gcc中的工作

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

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

所以相应的下面的代码:

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

只需按照上述内容打印“Hi”一次。

但是在Linux上执行上面的代码,遵循gcc,它打印两次 “Hi”。

有人可以向我解释使用fork()实际上发生了什么,如果我已经正确理解fork()工作吗?

Solutions Collecting From Web of "fork()在linux gcc中的工作"

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

fork的两边都继承未刷新的缓冲区,所以当fork的每一边碰到return语句并结束时,它将被刷新两次。

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

标准输出到屏幕(而不是当你重定向到一个文件)实际上是缓冲行结束,所以如果你做了printf("Hi\n"); 你不会有这个问题,因为它会刷新缓冲区本身。

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

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

另外,正如我所说,如果你在你的printf包含一个换行符,它也应该工作:

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

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

这包括C标准库。

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

fork()导致这个问题的例子

  • stdio如tty输入/输出,管道,光盘文件
  • 例如数据库客户端库使用的套接字
  • 服务器进程使用的套接字 – 当一个服务于一个套接字的子服务器恰好继承了anohter的文件句柄时,可能会产生奇怪的效果 – 正确地获得这种编程是非常棘手的,请参阅Apache源代码中的示例。

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

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

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

c)分手后,以某种方式清理所有的物体,使他们全部处于健全的状态,然后让孩子继续。 例如关闭底层的文件描述符,而不需要清除父文件中复制的缓冲区中的数据。 这是棘手的。

c)大约是Apache所做的。

printf()会缓冲。 你有没有尝试打印到stderr

技术答案:

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

一些图书馆有一个fflushall(),你可以在fork()之前调用它来使孩子的stdio安全。 在这种情况下,它也会使exit()安全,但在一般情况下不是这样。