【问题标题】:The difference between fork(), vfork(), exec() and clone()fork()、vfork()、exec()和clone()的区别
【发布时间】:2011-06-18 21:08:28
【问题描述】:

我希望在 Google 上找到这四个之间的区别,并且我预计会有大量关于这方面的信息,但是这四个调用之间确实没有任何可靠的比较。

我开始尝试编译一种基本的概览,看看这些系统调用之间的差异,这就是我得到的。所有这些信息是否正确/我是否遗漏了任何重要的信息?

Fork:fork 调用基本上复制了当前进程,几乎在所有方面都相同(并非所有内容都被复制,例如,某些实现中的资源限制,但想法是创建尽可能接近的副本可能)。

新进程(子进程)获得不同的进程 ID(PID),并以旧进程(父进程)的 PI​​D 作为其父进程 PID(PPID)。因为这两个进程现在运行的代码完全相同,所以它们可以通过 fork 的返回码来判断哪个是哪个——子进程得到 0,父进程得到子进程的 PID。这就是全部,当然,假设 fork 调用有效 - 如果没有,则不会创建子节点并且父节点会收到错误代码。

Vfork : vfork 和 fork 的基本区别在于,当使用 vfork() 创建新进程时,父进程会暂时挂起,子进程可能会借用父进程的地址空间。这种奇怪的情况一直持续到子进程退出,或者调用 execve(),此时父进程 进程继续。

这意味着 vfork() 的子进程必须小心避免意外修改父进程的变量。特别是子进程不能从包含vfork()调用的函数返回,也不能调用exit()(如果需要退出,应该使用_exit();其实对子进程也是如此一个普通的 fork())。

Exec : exec 调用是一种基本上用新程序替换整个当前进程的方法。它将程序加载到当前进程空间并从入口点运行它。 exec() 用函数指向的可执行文件替换当前进程。除非出现 exec() 错误,否则控制永远不会返回到原始程序。

Clone :克隆,作为fork,创建一个新进程。与 fork 不同,这些调用允许子进程与调用进程共享其部分执行上下文,例如内存空间、文件描述符表和信号处理程序表。

当使用clone创建子进程时,它会执行函数应用程序fn(arg)。 (这与 fork 不同,后者从原始 fork 调用开始在子进程中继续执行。) fn 参数是一个指向函数的指针,该函数由子进程在其执行开始时调用。 arg 参数被传递给 fn 函数。

当 fn(arg) 函数应用程序返回时,子进程终止。 fn 返回的整数是子进程的退出代码。子进程也可以通过调用 exit(2) 或在收到致命信号后显式终止。

信息获取形式:

感谢您抽出宝贵时间阅读本文! :)

【问题讨论】:

  • 为什么vfork不能调用exit()?还是不回来? exit() 不只使用_exit() 吗?我也在努力理解:)
  • @Gnuey:因为它可能(如果它的实现方式不同于fork(),它在 Linux 中,可能是所有 BSD 中)借用其父级的地址空间。它所做的任何事情,除了调用execve()_exit() 之外,都有很大的可能会弄乱父母。特别是,exit() 调用 atexit() 处理程序和其他“终结器”,例如:它刷新 stdio 流。从vfork() 孩子返回​​可能(与以前相同的警告)会弄乱父母的堆栈。
  • 我想知道父进程的线程会发生什么;它们都是克隆的还是只有调用fork 系统调用的线程?
  • @LazerSharks vfork 产生一个类似线程的进程,其中内存在没有写时复制保护的情况下共享,因此执行堆栈操作可能会破坏父进程。

标签: linux process fork exec clone


【解决方案1】:
  1. fork() - 创建一个新的子进程,它是父进程的完整副本。子进程和父进程使用不同的虚拟地址空间,最初由相同的内存页填充。然后,随着两个进程的执行,虚拟地址空间开始变得越来越不同,因为操作系统对这两个进程中的任何一个正在写入的内存页面执行惰性复制,并为修改后的页面分配一个独立的副本。每个进程的内存。这种技术称为 Copy-On-Write (COW)。
  2. vfork() - 创建一个新的子进程,它是父进程的“快速”副本。与系统调用fork() 相比,子进程和父进程共享相同的虚拟地址空间。笔记!使用相同的虚拟地址空间,父子都使用相同的堆栈,堆栈指针和指令指针,就像经典的fork()!为了防止使用相同堆栈的父进程和子进程之间发生不必要的干扰,父进程的执行被冻结,直到子进程调用exec()(创建新的虚拟地址空间并转换到不同的堆栈)或_exit() (终止流程执行)。 vfork()fork() 对“fork-and-exec”模型的优化。它的执行速度比fork() 快4-5 倍,因为与fork() 不同(即使考虑到COW),vfork() 系统调用的实现不包括创建新地址空间(新页面目录的分配和设置)。
  3. clone() - 创建一个新的子进程。该系统调用的各种参数,指定父进程的哪些部分必须复制到子进程中,哪些部分将在它们之间共享。因此,该系统调用可用于创建各种执行实体,从线程开始,到完全独立的进程完成。事实上,clone() 系统调用是用于实现pthread_create() 和所有fork() 系统调用家族的基础。
  4. exec() - 重置进程的所有内存,加载和解析指定的可执行二进制文件,设置新堆栈并将控制权传递给加载的可执行文件的入口点。此系统调用永远不会将控制权返回给调用者,并用于将新程序加载到现有进程中。这个系统调用与fork() 系统调用一起形成了一个经典的 UNIX 进程管理模型,称为“fork-and-exec”。

【讨论】:

  • 请注意,vfork 的 BSD 和 POSIX 要求非常弱,因此将 vfork 设为 fork 的同义词是合法的(并且 POSIX.1-2008 将 vfork 从完全符合规范)。如果你碰巧在一个同义词的系统上测试你的代码(例如除了 NetBSD 之外的大多数 post-4.4 BSD、pre-2.2.0-pre6 Linux 内核等),即使你违反了vfork 合同,它也可能工作,然后如果你在别处运行它就会爆炸。一些用fork(例如OpenBSD)模拟它的人仍然保证父级不会恢复运行,直到子级execs 或_exits。它非常不便携。
  • 关于您第三点的最后一句话:我注意到在 Linux 上使用 strace 时,虽然 fork() 的 glibc 包装器确实调用了克隆系统调用,但 vfork() 的包装器调用了 vfork 系统调用跨度>
【解决方案2】:

fork()、vfork() 和 clone() 都调用 do_fork() 来做真正的工作,但参数不同。

asmlinkage int sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.esp, &regs, 0);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
}
#define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_VM    0x00000100  /* set if VM shared between processes */

SIGCHLD means the child should send this signal to its father when exit.

对于fork,子进程和父进程有独立的VM页表,但是为了效率,fork不会真正复制任何页面,它只是将所有可写页面设置为子进程只读。因此,当子进程想要在该页面上写入内容时,会发生页面异常,并且内核将分配从具有写权限的旧页面克隆的新页面。这就是所谓的“写时复制”。

对于vfork来说,虚拟内存完全是由child和father——正因为如此,father和child不可能同时唤醒,因为它们会相互影响。因此,父亲将在“do_fork()”结束时睡觉,并在孩子调用 exit() 或 execve() 时醒来,此后它将拥有新的页表。这是父亲睡觉的代码(在do_fork()中)。

if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;

这是唤醒父亲的代码(在 mm_release() 中由 exit() 和 execve() 调用)。

up(tsk->p_opptr->vfork_sem);

对于 sys_clone(),它更加灵活,因为您可以向其中输入任何 clone_flags。所以 pthread_create() 用很多 clone_flags 调用这个系统调用:

int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM);

总结:fork()、vfork() 和 clone() 将创建与父进程共享资源不同挂载的子进程。我们也可以说 vfork() 和 clone() 可以创建线程(实际上它们是进程,因为它们具有独立的 task_struct),因为它们与父进程共享 VM 页表。

【讨论】:

    【解决方案3】:

    在 fork() 中,子进程或父进程将根据 cpu 选择执行。 但是在 vfork() 中,child 肯定会先执行。子进程终止后,父进程将执行。

    【讨论】:

    • 错了。 vfork() 可以实现为 fork()
    • 在 AnyFork() 之后,没有定义谁运行第一个父/子。
    • @Raj:如果你认为在分叉后有一个隐含的顺序概念,那么你就有一些概念上的误解。分叉创建一个 new 进程,然后将控制权返回给两个进程(每个进程返回一个不同的pid)——如果这样的事情有意义(例如多个处理器)。如果出于某种原因您需要这些进程以特定的串行顺序执行,那么您需要额外的同步,而分叉不提供;坦率地说,你可能一开始就不需要叉子。
    • 其实@AjayKumarBasuthkar 和@ninjalj,你们都错了。使用vfork(),孩子先跑。它在手册页中;父母的执行被暂停,直到孩子死亡或execs。 ninjalj 查找内核源代码。没有办法将vfork() 实现为fork(),因为它们在内核中将不同的参数传递给do_fork()。但是,您可以使用 clone 系统调用实现 vfork
    • @ZacWimer:请参阅 ShadowRanger 对另一个答案的评论 stackoverflow.com/questions/4856255/… 旧 Linux 确实 将它们同义化,因为显然除了 NetBSD 之外的 BSD(它往往被移植到很多非-MMU系统)做。来自 Linux 手册页: 在 4.4BSD 中,它成为 fork(2) 的同义词,但 NetBSD 再次引入了它;见⟨netbsd.org/Documentation/kernel/vfork.html⟩。在 Linux 中,一直等到 fork(2) 到 2.2.0-pre6 左右。
    【解决方案4】:
    • execve() 将当前可执行映像替换为从可执行文件加载的另一个映像。
    • fork() 创建一个子进程。
    • vfork()fork() 的历史优化版本,用于在 fork() 之后直接调用 execve() 时使用。事实证明,它在非 MMU 系统中运行良好(fork() 无法以有效的方式工作)以及当fork()ing 进程占用大量内存以运行一些小程序时(想想 Java 的 Runtime.exec())。 POSIX 已将posix_spawn() 标准化,以取代vfork() 的后两种更现代的用法。
    • posix_spawn() 相当于 fork()/execve(),并且还允许在两者之间进行一些 fd 杂耍。它应该取代 fork()/execve(),主要用于非 MMU 平台。
    • pthread_create() 创建一个新线程。
    • clone() 是一个特定于 Linux 的调用,可用于实现从 fork()pthread_create() 的任何内容。它提供了很多控制权。灵感来自rfork()
    • rfork() 是特定于 Plan-9 的呼叫。它应该是一个通用调用,允许在完整进程和线程之间进行多种程度的共享。

    【讨论】:

    • 感谢您添加比实际要求更多的信息,它帮助我节省了时间
    • Plan 9 就是这样一个笑话。
    • 对于那些不记得MMU是什么意思的人:“内存管理单元”-进一步阅读on Wikipedia
    【解决方案5】:
    • vfork() 是过时的优化。在良好的内存管理之前,fork() 制作了父内存的完整副本,因此非常昂贵。因为在许多情况下fork() 后面跟着exec(),它会丢弃当前的内存映射并创建一个新的映射,所以这是不必要的开销。如今,fork() 不复制内存;它只是设置为“写入时复制”,因此fork()+exec()vfork()+exec() 一样有效。

    • clone()fork() 使用的系统调用。使用一些参数,它创建一个新进程,使用其他参数,它创建一个线程。它们之间的区别只是哪些数据结构(内存空间、处理器状态、堆栈、PID、打开的文件等)是共享的。

    【讨论】:

    • vfork 避免了临时提交更多内存以便执行exec 的需要,并且它仍然比fork 更有效,即使没有那么高的程度。因此,人们可以避免过度使用内存,这样一个庞大的大程序就可以产生一个子进程。因此,这不仅是性能提升,而且可能完全可行。
    • 实际上,当你的 RSS 很大时,我亲眼目睹了 fork() 是多么不便宜。我想这是因为内核仍然需要复制所有的页表。
    • 它必须复制所有页表,在两个进程中设置所有可写内存copy-on-write,刷新TLB,然后它必须恢复所有更改在exec 上发送给父级(并再次刷新 TLB)。
    • vfork 在 cygwin(一个模拟 dll 的内核,在 Microsoft 的 Windows 之上运行)中仍然有用。 cygwin 无法实现高效的 fork,因为底层操作系统没有。
    猜你喜欢
    • 2011-05-14
    • 2017-04-12
    • 2010-12-11
    • 2012-08-01
    • 2015-10-28
    • 2023-04-03
    • 1970-01-01
    • 2015-12-01
    相关资源
    最近更新 更多