为什么fork()以它的方式工作

use*_*779 33 linux fork process

所以,我已经习惯了fork(),我知道它做了什么.作为初学者,我非常害怕它(我仍然不完全理解它).fork()你可以在网上找到的一般描述是,它复制当前进程并分配不同的PID,父PID和进程将有不同的地址空间.一切都很好,但是,鉴于这个功能描述,初学者会想知道"为什么这个功能如此重要......为什么我要复制我的过程?".所以我确实很奇怪,最终我发现你可以通过execve()家庭来调用当前流程中的其他流程.

我仍然不明白为什么你必须这样做?最合乎逻辑的是拥有一个可以调用的函数

create_process("executable_path+name",params..., more params); 
Run Code Online (Sandbox Code Playgroud)

这会生成一个新进程并在main()的开头运行它并返回新的PID.

困扰我的是fork/execve解决方案正在进行可能不需要的工作的感觉.如果我的进程使用大量内存怎么办?内核是否复制了我的页面表等.我确信它不会真正分配实际内存,除非我触及它.另外,如果我有线程会发生什么?在我看来,它太乱了.

几乎所有关于fork的描述,比如它只是复制进程并且新进程在fork()调用之后开始运行.这确实发生了什么,但为什么会这样发生?为什么fork/execve是产生新进程的唯一方法,以及从当前创建新进程的最常用的unix方法是什么?有没有其他更有效的方法来产生进程?**这不需要复制更多的内存.

这个帖子谈到同样的问题,但我发现它不太令人满意:

谢谢.

nin*_*alj 20

这是由于历史原因.正如https://www.bell-labs.com/usr/dmr/www/hist.html所解释的那样,早期的Unix确实既没有fork()也没有exec*(),并且shell执行命令的方式如下:

  • 进行必要的初始化(打开stdin/stdout).
  • 阅读命令行.
  • 打开命令,加载一些引导代码并跳转到它.
  • 引导代码读取打开的命令,(覆盖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()有效地支持'ed进程.

其他效率低下的原因fork()是它最初复制了父级的地址空间(和页表),这可能使得从大型程序运行的短程序相对较慢,或者可能使操作系统拒绝fork()认为可能没有足够的内存用于它(要解决此问题,您可以增加交换空间,或更改操作系统的内存过载设置).作为一则轶事,Java 7用于vfork()/posix_spawn()避免这些问题.

另一方面,fork()使创建同一进程的多个实例非常有效:例如:Web服务器可能有几个相同的进程为不同的客户端提供服务.其他平台也支持线程,因为产生不同进程的成本比复制当前进程的成本要大得多,这可能比生成新线程的成本要大一些.这是不幸的,因为共享 - 所有线程都是错误的磁铁.


Bas*_*tch 10

请记住,这fork是在Unix(或许之前)很早就在机器上发明的,这些机器现在看起来非常小(例如64K字节的内存).

它与通过最基本的可能行动提供基本机制而非政策的整体(原始)理念更加同步.

fork只是创建一个新流程,最简单的思维方式就是克隆当前流程.所以fork语义是非常自然的,它是最简单的机制.

其他系统调用(execve)负责加载新的可执行文件等.

将它们分开(并且也提供pipedup2系统调用)给出了很大的灵活性.

并且在当前系统上,fork非常有效地实现(通过写分页技术的懒惰副本).众所周知,该fork机制使得Unix进程创建速度非常快(例如,比在Windows或VAX/VMS上更快,系统调用创建的进程更类似于您的建议).

还有vfork系统调用,我不打扰使用.

并且posix_spawn API比复杂forkexecve单独复杂得多,因此说明fork更简单...


pau*_*sm4 5

"fork()"是一项出色的创新,它通过单一API解决了一大类问题.它是在多处理不常见的时候发明的(并且在你和我今天使用的那种多处理之前大约有二十年).


urb*_*ter 0

就分页/虚拟内存而言,有些技术中 fork() 并不总是复制进程的整个地址空间。存在写时复制,其中分叉进程获取与其父进程相同的地址空间,然后仅复制(由任一进程)更改的空间的一部分。