为什么fork()是这样工作的

因此,我使用了fork(),我知道它的作用。作为一个初学者,我非常害怕它(我仍然不完全理解它)。您可以在线找到的fork()的一般描述是,它复制当前进程并分配不同的PID、父PID,进程将具有不同的地址空间。所有这些都是好的,但是,鉴于这个功能描述,初学者可能会想“为什么这个功能如此重要……为什么我要复制我的流程?”。所以我很好奇,最终我发现这就是如何通过execve()家族从当前进程中调用其他进程的方法

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

创建进程(“可执行路径+名称”,参数…,更多参数);

这将产生一个新进程,并在main()的开头开始运行它,然后返回新的PID

让我感到困扰的是,fork/execve解决方案正在做可能不需要的工作。如果我的进程使用了大量内存怎么办?内核是否复制我的页面表等。我确信它不会真正分配真正的内存,除非我触摸过它。还有,如果我有线程会发生什么?我只是觉得太乱了

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

这篇文章讨论了同样的问题,但我发现它不太令人满意:

多谢各位

这是历史原因造成的。正如在https://www.bell-labs.com/usr/dmr/www/hist.html,很早的Unix既没有fork()也没有exec*(),shell执行命令的方式是:

  • 执行必要的初始化(打开stdin/stdout)
  • 阅读命令行
  • 打开命令,加载一些引导代码并跳转到它
  • 引导代码读取打开的命令(覆盖shell的内存),然后跳转到它
  • 命令结束后,它将调用exit(),然后通过重新加载shell(覆盖命令的内存)并跳转到shell,返回到步骤1

从这里开始,fork()是一个简单的添加(27条装配线),重用了代码的其余部分

在Unix开发的那个阶段,执行命令变成:

  • 阅读命令行
  • fork()一个子进程,然后等待它(通过向它发送消息)
  • 子进程加载命令(覆盖子进程的内存),并跳转到该命令
  • 一旦命令结束,它将调用exit(),这现在更简单了。它只是清理了它的进程条目,并放弃了控制

最初,fork()没有进行写时复制。由于这使得fork()非常昂贵,而且fork()经常被用来产生新的进程(通常紧接着是exec*()),因此出现了fork()的优化版本:vfork(),它在父级和子级之间共享内存。在vWork()的那些实现中,父级将被挂起,直到子级exec*()‘ed或\u exit()‘ed,从而放弃父级的内存。后来,fork()被优化为写时复制,仅当内存页在父级和子级之间开始不同时才复制内存页vWork()后来又看到了对端口的新兴趣!MMU系统(例如:如果你有一个ADSL路由器,它可能在一个!MMU MIPS CPU上运行Linux),它不能进行COW优化,而且不能有效地支持fork()”进程

fork()中效率低下的另一个原因是它最初复制了父级的地址空间(和页表),这可能会使从大型程序运行短程序的速度相对较慢,或者可能会使操作系统拒绝fork()认为可能没有足够的内存(为了解决这个问题,您可以增加交换空间,或者更改操作系统的内存过多设置)。作为一个轶事,Java7使用vWork()/posix_spawn()来避免这些问题

另一方面,fork()使创建同一进程的多个实例变得非常高效:例如:一台web服务器可能有多个相同的进程为不同的客户端服务。其他平台更青睐线程,因为生成不同进程的成本远高于复制当前进程的成本,而复制当前进程的成本可能只比产生一个新线程。这是不幸的,因为共享所有线程都是错误的磁石

发表评论