为什么fork()函数能够按照它的方式工作

42

所以,我使用了fork(),并且知道它的作用。作为一个初学者,我对它感到很害怕(而且我仍然没有完全理解它)。在网上可以找到的fork()的一般描述是,它会复制当前进程并分配不同的PID、父PID,并且该进程将具有不同的地址空间。这都很好,然而,根据这个功能描述,一个初学者会想知道“为什么这个函数如此重要……为什么我要复制我的进程呢?”于是我开始思考,最终我发现这就是通过execve()家族在当前进程中调用其他进程的方法。

我仍然不明白为什么你必须用这种方式来做?最合乎逻辑的事情应该是有一个你可以像调用其他函数一样调用的函数

create_process("executable_path+name",params..., more params); 

该方法将产生一个新进程并在main()函数的开始运行它,并返回新的PID。

让我担心的是fork/execve方法可能会做一些不必要的工作。如果我的进程正在使用大量内存,内核会复制我的页表等信息吗?除非我已经使用了它,否则我敢肯定它实际上没有分配真正的内存。此外,如果我有线程会怎么样?对我来说,这似乎太混乱了。

几乎所有关于fork的描述都说它只是复制进程,并且在fork()调用之后启动新进程运行。这确实是发生的事情,但为什么会这样发生,为什么fork/execve是生成新进程的唯一方法,以及从当前进程创建新进程的最常见Unix方式是什么?是否有其他更有效的生成进程的方法?**这些方法不需要复制更多的内存。

这个主题讨论了同样的问题,但我觉得不太令人满意:

谢谢。


请发布在 http://unix.stackexchange.com/ 或 http://superuser.com/ 上。 - rlemon
10
为什么选择Unix?这是一个编程问题,应该发布到Stack Overflow上。 - Petr
请参考 http://cm.bell-labs.com/who/dmr/hist.html 进行解释。 - ninjalj
2
阅读了vfork的man页面后,我认为你是正确的。fork确实有一些不必要的行为。因此,在普通情况下,vfork更好,只有在非常特殊的情况下需要使用fork,例如子进程需要父进程的某些内存结构,并且不执行任何其他ELF文件。 - lovespring
链接已失效,但我怀疑http://www.landley.net/history/mirror/unix/dmr/hist.html是同一份文档。 - Douglas Leeder
14个回答

1

如果底层实现使用写时复制寻址系统,那么fork()函数可以使用非常少的内存分配来实现。但是,使用该优化无法实现create_process函数。


1

使用fork的主要原因是执行速度。

如果按照您建议的方式,使用一组参数启动新进程,则新进程需要解析这些参数并重复大部分父进程已完成的处理。使用“fork()”,父进程的完整副本立即可用于子进程,其中包括所有已解析和格式化的内容。

此外,在大多数情况下,程序将是“.so”或“.dll”,因此只会复制堆栈和堆存储,而不会复制可执行指令。


0

你可以将其类比为在Windows中生成线程,但进程不共享资源,除了文件句柄、共享内存和其他明确可继承的东西。因此,如果您有一个新任务要完成,您可以进行分叉,其中一个进程继续执行其原始工作,而克隆体则负责新任务。

如果您想进行并行计算,您的进程可以在循环正上方将自己分成多个克隆体。每个克隆体执行一部分计算,而父进程等待它们完成。操作系统确保它们可以并行运行。在Windows中,您需要使用OpenMP来获得相同的表达能力。

如果您需要从文件中读取或写入数据,但不能等待,您可以使用fork并克隆一个进程,在您继续原始任务的同时,克隆进程进行I/O操作。在Windows上,您可能会考虑在许多情况下生成线程或使用重叠I/O,而在Unix中,简单的fork就足够了。特别是,与线程相比,进程没有相同的可扩展性问题。这在32位系统上尤其如此。仅仅使用fork比处理重叠I/O的复杂性要方便得多。虽然进程有自己的内存空间,但线程存在于同一内存空间中,因此应考虑将多少个线程放入32位进程中。使用fork制作32位服务器应用程序非常简单,而使用线程制作32位服务器应用程序可能会成为噩梦。因此,如果您正在32位Windows上编程,则必须采用其他解决方案,例如重叠I/O,这很麻烦。
由于进程不像线程那样共享全局资源(例如malloc中的全局锁),因此具有更高的可扩展性。虽然线程经常会互相阻塞,但进程则可以独立运行。
在Unix上,由于fork会创建一个写时复制克隆进程,因此它不比在Windows上生成新线程更加重量级。
如果你处理解释性语言,那里通常有全局解释器锁(Python、Ruby、PHP...),具备分叉能力的操作系统是必不可少的。否则,你利用多个处理器的能力就会受到更大的限制。
还有一件事情是安全问题。进程不共享内存空间,也不能互相干扰内部细节,这导致更高的稳定性。如果你有一个使用线程的服务器,一个线程崩溃会导致整个服务器应用程序崩溃。通过分叉,一个崩溃只会使分叉的克隆版本崩溃。这也使得错误处理更加简单化。通常可以让你的分叉克隆版本中止,因为它对原始应用程序没有任何影响。
还存在一个安全问题。如果一个分叉进程被注入恶意代码,它无法进一步影响父进程。现代Web浏览器利用这一点来保护一个标签页免受另一个标签页的影响。如果你拥有一个分叉系统调用,所有这些都更容易编程实现。

0
在分页/虚拟内存方面,有一些技术可以使fork()函数不总是复制整个进程的地址空间。其中一种技术是写时复制(copy on write),即分支进程获得与父进程相同的地址空间,并且只会复制被任一进程修改的部分空间。

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