因此,我使用了fork()
,我知道它的作用。作为一个初学者,我非常害怕它(而且我仍然不完全了解它)。您可以在线找到的fork()
的一般描述是,它复制当前进程并分配不同的PID,父PID且该进程将具有不同的地址空间。一切都很好,但是,鉴于此功能描述,初学者会想知道“为什么这个功能如此重要...我为什么要复制我的过程?”。因此,我确实纳闷,最终我发现这就是您可以通过execve()
系列从当前进程中调用其他进程的方式。
我仍然不明白的是,为什么你必须这样做呢?最合乎逻辑的事情是拥有一个可以调用的函数,例如
create_process("executable_path+name",params..., more params);
这将产生一个新进程,并在main()的开头开始运行它并返回新的PID。
让我感到困扰的是,fork/execve解决方案正在做可能不需要的工作。如果我的过程正在使用大量内存怎么办?内核是否复制我的页表等。我确信除非我摸过它,否则它不会真正分配实际内存。另外,如果我有线程怎么办?在我看来,这太乱了。
几乎所有关于fork的描述都说,它只是复制了进程,并且新进程在
fork()
调用之后开始运行。这确实是发生了什么,但是为什么会这样发生,为什么fork/execve是产生新进程的唯一方法,以及从当前进程创建新进程的最通用的unix方法是什么?还有其他更有效的方法来生成进程吗?**不需要复制更多内存。This线程讨论相同的问题,但我发现它并不令人满意:
谢谢。
最佳答案
这是由于历史原因。如https://www.bell-labs.com/usr/dmr/www/hist.html所述,很早以前的Unix既没有fork()
也没有exec*()
,shell执行命令的方式是:
exit()
,然后通过重新加载 shell 程序(覆盖命令的内存)并跳转至步骤1来工作。从那里开始,
fork()
是一个简单的添加(27条组装线),重用了其余的代码。在Unix开发的那个阶段,执行命令成为:
fork()
一个子进程,并等待它(通过向其发送消息)。 exit()
,它现在更简单了。它只是清除了其流程条目,并放弃了控制。 最初,
fork()
不会在写入时进行复制。由于这使fork()
变得昂贵,并且fork()
通常用于产生新进程(因此通常紧随其后是exec*()
),因此出现了fork()
的优化版本:vfork()
,它在父级和子级之间共享内存。在vfork()
的那些实现中,父级将被挂起,直到子exec*()
'ed或_exit()
'ed,从而放弃了父级的内存。后来,fork()
经过优化,可以在写入时进行复制,仅当内存页面的父子之间开始有所不同时才进行复制。 vfork()
后来对!MMU系统的端口重新产生了兴趣(例如:如果您有ADSL路由器,则它可能在!MMU MIPS CPU上运行Linux),它无法进行COW优化,而且不支持fork()
高效处理。fork()
效率低下的另一个原因是,它最初会复制父级的地址空间(和页表),这可能会使大型程序中运行的短程序相对较慢,或者可能使操作系统以为可能没有足够的内存来拒绝fork()
。为此(要解决此问题,您可以增加交换空间,或更改操作系统的内存过量使用设置)。作为轶事,Java 7使用vfork()/posix_spawn()
避免了这些问题。另一方面,
fork()
使创建同一流程的多个实例非常有效:例如:Web服务器可能具有为不同客户端提供服务的多个相同流程。其他平台更喜欢线程,因为产生另一个进程的开销比复制当前进程的开销大得多,后者可能比产生新线程的开销大一点。不幸的是,因为所有共享线程都会吸引错误。关于linux - 为什么fork()以它的方式工作,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8292217/