【问题标题】:What is the point of the process fork creates being a copy of the parent?分叉创建的进程作为父进程的副本有什么意义?
【发布时间】:2014-09-16 18:53:36
【问题描述】:

我知道“为什么会这样”的答案是因为语言是这样发明的,但是 fork() 生成调用它的过程的副本似乎是浪费了很多精力。也许有时它很有用,但大多数时候肯定有人想开始一个新进程,而不是与调用进程重复吗?为什么 fork 创建一个相同的进程,而不是一个空的或通过传递参数定义的?

来自yolinux

fork() 系统调用将产生一个新的子进程,它是 与父进程相同的进程,但具有新的系统进程 身份证

换句话说,什么时候从父进程的副本开始有用?

【问题讨论】:

  • 一个显而易见的答案:Apache 网络服务器。一个父级“控制” apache 的实例派生了许多子级,这些子级是实际处理 http 请求的那些子级。为父进程支付一次启动成本并搭载子进程的成本要低得多
  • 如果不是父进程的副本,新进程会运行什么代码?
  • @MarkRansom:一些系统支持“spawn”调用,它要求操作系统内核创建一个具有指定可执行文件的新进程。例如,这就是 Windows 上的 CreateProcess 所做的。
  • @nneonneo 跟上 POSIX 语义,我们应该参考各种 exec() 变体,它们将取代实际进程的代码空间(而不是像 system() 这样的调用那样启动新进程做)。
  • @MarkRansom OTOH,通常在分叉后直接调用exec

标签: c++ c fork


【解决方案1】:

在子进程中复制父进程的一大优点是它允许父程序在执行子进程之前对其环境进行自定义。例如,父进程可能想要读取子进程'stdout,在这种情况下,它需要设置管道,以便在执行新程序之前读取该子进程。 p>

在效率方面,它也没有听起来那么糟糕。整个过程是在 Linux 上使用进程内存的写时复制语义实现的(手册页中指出的特殊情况除外):

在 Linux 下(以及自第 7 版以来的大多数 unice,现在所有 unice 的父级),fork() 是使用写时复制页面实现的,因此唯一的 它招致的惩罚是复制 父母的页表(也可以是写时复制),并为孩子创建独特的任务结构。

【讨论】:

  • 性能影响不容小觑,这就是为什么会有vfork()
  • vfork() 确实存在,但我想不出使用它的充分理由(尤其是在您阅读了 linux 手册页的 BUGS 部分之后)。
  • vfork 基本没用。它太受限制且太特定于平台。 fork 在 Linux 上运行得非常好;如果你真的需要速度,clone 的 bug 比 vfork 少得多。
  • 这个答案并没有真正向我解释动机或必要性。如果子进程不使用父进程可执行文件,父进程就不能修改子进程的环境吗?
【解决方案2】:

fork 系统调用有一些非常合法的用途。以下是几个例子:

  • 节省内存。因为任何现代 UNIX/Linux 系统上的fork 在子进程和父进程之间共享内存(通过写时复制语义),父进程可以加载一些静态数据,这些数据可以立即共享给子进程。 Android 上的 zygote 进程执行此操作:它预加载 Java (Dalvik) 运行时和许多类,然后根据需要简单地创建新的应用程序进程(继承父运行时和加载类的副本)。
  • 节省时间。一个进程可以执行一些昂贵的初始化过程(例如 Apache 加载配置文件和模块),然后fork 关闭工作人员以执行使用预加载的初始化数据的任务。
  • 任意流程定制。在具有直接进程创建方法的系统上(例如带有CreateProcess 的Windows、带有spawn 的QNX 等,这些直接进程创建API 往往非常复杂,因为必须在函数调用中指定每个可能的进程自定义相比之下,使用fork/exec,进程可以通过标准系统调用(closesignaldup 等)执行自定义,然后在准备就绪时使用execfork/exec因此,它是现有的最简单的流程创建 API 之一,同时也是最强大和最灵活的 API 之一。

公平地说,fork 也有不少问题。例如,它不适用于多线程程序:在新进程中只创建了一个线程,并且锁没有正确关闭(导致atfork 处理程序必须在fork 上重置锁状态)。

【讨论】:

  • 有一种观点认为线程是邪恶的。这意味着上面提到的问题不是 fork 的问题,而是线程的问题。
  • 在一个通过克隆创建廉价进程的系统中,线程是不必要的。
【解决方案3】:

出乎所有人的意料,主要是fork 让 Unices 上的流程创建速度如此之快。

AFAIK,在 Linux 上,实际进程内存不会在 fork 时复制,子进程从与父进程相同的虚拟内存映射开始,并且仅在子进程进行更改的位置和时间复制页面。无论如何,大多数页面都是只读代码,因此它们永远不会被复制。这称为copy-on-write

复制父进程有用的用例:

  1. 贝壳

当您说cat foo >bar 时,shell 分叉,并在子进程(仍然是 shell)中准备重定向,然后执行 cat foo。执行的程序在与子 shell 相同的 PID 下运行,并继承所有打开的文件描述符。你不会相信编写一个基本的 Unix shell 是多么容易。

  1. 守护进程(服务)

守护程序在后台运行。他们中的许多人在一些初步准备后分叉,父母退出,孩子从终端分离并继续在后台运行。

  1. 网络服务器

许多网络守护程序必须同时处理多个连接。示例 sshd。主守护进程以 root 身份运行并监听端口 22 上的新连接。当有新连接进入时,它会分叉一个子进程。孩子只是保留代表该连接的新套接字,验证用户,放弃权限等等。

【讨论】:

    【解决方案4】:

    为什么是fork()?它与 C 无关。C 本身只是在那个时候才出现的。这是因为原始 UNIX 内存页面和进程管理的工作方式,导致进程被调出,然后在不同的位置调入,而不卸载进程的第一个副本是微不足道的。

    Unix 分时系统的演变 (http://cm.bell-labs.com/cm/cs/who/dmr/hist.html) 中,Dennis Ritchie 说:“事实上,PDP-7 的 fork 调用恰好需要 27 行汇编代码。 ”查看更多链接。

    线程是邪恶的。使用线程,您基本上拥有多个进程,它们都可以访问相同的内存空间,它们可以在彼此的值上跳舞。根本没有内存保护。请参阅 Unix 编程的艺术,第 7 章 (http://www.faqs.org/docs/artu/ch07s03.html#id2923889) 以获得更完整的解释。

    【讨论】:

    • +1,这是对原始问题的答案:为什么要这样设计。
    • 加一个作为历史参考(但不是用于恶意线程?)
    猜你喜欢
    • 2018-07-02
    • 2011-05-10
    • 1970-01-01
    • 2017-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-08
    • 2014-12-30
    相关资源
    最近更新 更多