Linux的fork函数与Windows的CreateProcess函数相比,会复制哪些内容?

9

我正在将Windows应用程序移植到Linux。在Windows上,我使用CreateProcess来运行子进程并重定向所有标准流(输入、输出和错误)。流重定向非常重要,主进程向子进程发送数据并接收它们的输出和错误消息。主进程是一个很大的进程,有很多内存和线程,而子进程则很小。在Linux上,我发现fork函数具有与Windows上的CreateProcess类似的功能。然而,手册说fork“创建父进程的副本”,包括代码、数据和堆栈。这是否意味着,如果我创建一个巨大的进程副本,该进程使用1 GB的内存只是为了运行一个占用1 MB内存的非常简单的命令行工具,我需要首先使用fork复制1 GB的内存,然后用1 MB的进程替换这1 GB?因此,如果我有100个线程,就需要有100 GB的内存来运行需要只占用100 MB内存的100个进程?还有其他不知道fork执行的父进程中的线程会做什么?fork函数在幕后究竟做了什么,它真的是从庞大的父进程创建大量小的子进程的有效方式吗?


3
现代Unix系统在进行进程复制时使用“写时复制”,因此将会暂时分配内存以创建完整的进程副本,但新进程只存在于交换空间中。如果您使用不提供任何交换空间的VPS,则需要两倍的内存,这有点糟糕。 - Paul Tomblin
@Angew,实际上确实如此。但是以一种高效的方式进行复制,因此您不会注意到复制需要任何时间。 - Alfe
5个回答

10

当你调用fork()函数时,最初只会复制你的虚拟内存(VM),并且所有页面都被标记为复制时写入(copy-on-write)。你的新子进程将拥有与父进程VM逻辑副本相同的副本,但在你实际开始写入之前它不会消耗任何额外的RAM。

至于线程,fork在子进程中仅创建一个类似于调用线程的副本的新线程。

另外,一旦你调用任何exec系列调用(我假设你想要),那么你的整个进程映像将被替换为一个新的映像,并且只保留文件描述符。

如果你的父进程有很多打开的文件描述符,那么建议你遍历/proc/self/fd并关闭在子进程中不需要的所有文件描述符。


这意味着,如果您有多个线程,那么只有执行fork的线程会被保留?信号量、互斥锁等会变得无效吗?毕竟,新进程仍然可以访问它们。或者只有在实际访问内存时它们才会被激活? - Devolus
我理解了,但这里还有一个问题:在代码示例中,我看到在exec返回后,可以读取exec返回的值(退出码),并将其传递给printf退出状态等。这是否意味着在exec返回后,子进程的内存会以某种方式得到恢复?如果是这样,那么当我从子进程调用exec时,它的内容是否被保存在某个地方,例如交换空间? - Vitalii
2
@Devolus 线程:是的,你只会收到调用fork的线程的副本。你将收到其他线程堆栈的写时复制副本,但不会执行它们的线程。信号量:取决于它们是否被放入私有或共享内存中。私有内存将提供写时复制页面,因此您的子进程将获得这些页面的副本。共享内存将在父进程和子进程之间保持共享。 - Sergey L.
1
@VASoftOnline exec 只有在出现错误时才会返回,如果加载了新的进程映像,则 exec 永远不会返回。 - Sergey L.
如果你的意思是父进程读取值,那么不,子进程的内存已经被释放了,只有它的退出状态还在那里——搜索“僵尸进程”。@VASoftOnline - loreb

3

fork函数基本上将进程分成两个,父进程和子进程在fork函数调用后继续执行。但是,在子进程中的返回值为0,而在父进程中的返回值为子进程的进程ID。

创建子进程非常快,因为它使用与父进程相同的页面。这些页面被标记为写时复制(COW),因此如果任一进程更改了页面,则不会影响另一个进程。一旦子进程存在,它通常会调用其中一个exec函数以替换自身为映像。Windows没有fork的等效物,而是CreateProcess调用只允许您启动新进程。

有一个名为clonefork替代方案,它使您在启动新进程时具有更多控制权。例如,您可以指定要在新进程中调用的函数。


2

拷贝是“写时复制”的,因此如果您的子进程不修改数据,则其除父进程之外不会使用任何内存。通常,在fork()之后,子进程会执行exec()来替换该进程的程序为另一个程序,然后所有内存都会被释放。


是的,但如果您已经安全地配置了Linux,则仍需要可用的虚拟内存页面。 如果在Linux下正确实现它们,最好使用posix_spawnposix_spawnp启动第二个进程。 - James Kanze

2
我没有使用过CreateProcess,但是fork()并不是进程的完全副本。它创建了一个子进程,但是子进程从父进程调用fork时的同一条指令开始执行,并从那里继续执行。
我建议查看Three Easy Pieces 操作系统书籍的第5章。这可能会帮助你入门,并找到你要寻找的子进程生成调用。

1
分叉的子进程几乎复制了父进程的所有设施:内存、描述符、文本等等。唯一的例外是父级线程,它们没有被复制。

这个概念很好。更准确的说法是:fork()创建一个新进程,该进程是旧进程的副本,并且恰好有一个线程处于使用fork()的线程所在的状态(堆栈等)。 - Alfe

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