fork(),vfork(),exec()和clone()之间的区别


198

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

我着手尝试汇编一下这些系统调用之间的区别的基本概况,这就是我得到的。所有这些信息是否正确/我是否缺少任何重要信息?

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

新进程(子进程)获得不同的进程ID(PID),并将旧进程(父进程)的PID作为其父进程PID(PPID)。因为这两个进程现在正在运行完全相同的代码,所以它们可以通过fork的返回码来确定哪个是哪个-子代为0,父代为子代的PID。当然,假设fork调用有效,这就是全部-如果无效,则不会创建任何子代,并且父代会获得错误代码。

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

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

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

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

当使用克隆创建子进程时,它将执行功能应用程序fn(arg)。(这与fork不同,fork从原始fork调用的位置开始在子级中继续执行。)fn参数是指向函数的指针,该函数在子进程执行开始时即被调用。arg参数传递给fn函数。

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

信息表:

感谢您抽时间阅读 !:)


2
为什么vfork不能调用exit()?还是不回来?不退出()仅使用_exit()吗?我也想了解:)
LazerSharks 2014年

2
@Gnuey:因为它有可能(如果它的实现与fork()Linux中的有所不同,并且可能是在所有BSD中)借用其父级的地址空间。除了调用execve()或以外_exit(),它所做的任何事情都极有可能使父母混乱。特别是,exit()调用atexit()处理程序和其他“终结器”,例如:它刷新stdio流。从vfork()孩子那里回来可能会(和以前一样警告)弄乱父母的书堆。
ninjalj 2014年

我想知道父进程的线程会怎样?它们都是克隆的还是仅调用forksyscall 的线程?
Mohammad Jafar Mashhadi 2014年

@LazerSharks vfork生成了一个类似于线程的进程,该进程在共享内存时没有写时复制保护,因此执行堆栈操作可能会破坏父进程。
詹森

Answers:


160
  • vfork()是过时的优化。在进行良好的内存管理之前,fork()请先复制父级的内存,因此非常昂贵。因为在许多情况下,a fork()之后是exec(),它会丢弃当前的内存映射并创建一个新的内存映射,因此这是不必要的开销。如今,fork()不复制内存;它只是设置为“写时复制”,因此fork()+ exec()vfork()+ 一样有效exec()

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



22
vfork避免了只是临时分配更多的内存,以便可以执行exec,而且它的效率比fork还要高,即使程度不高。因此,人们可以避免不得不过量使用内存,以使庞大的大型程序可以生成子进程。因此,不仅可以提高性能,还可以使其完全可行。
重复数据删除器

5
实际上,我亲眼目睹了RSS(RSS)很大时fork()远不便宜。我认为这是因为内核仍然必须复制所有页表。
马蒂娜·法拉利

4
它必须复制所有页表,在两个进程设置所有可写内存写时复制,刷新TLB,然后必须将所有更改还原到上级(并再次刷新TLB)exec
zwol

3
vfork在cygwin(运行于Microsoft Windows顶部的dll仿真内核)中仍然有用。cygwin无法实现有效的派生,因为基础操作系统没有。
ctrl-alt-delor

81
  • execve() 将当前可执行映像替换为从可执行文件加载的另一个映像。
  • fork() 创建一个子进程。
  • vfork()是的历史优化版本fork(),用于在execve()之后直接调用时使用fork()。事实证明,它在非MMU系统(fork()无法高效运行)中以及在fork()运行具有巨大内存占用量的进程以运行某些小程序(例如Java Runtime.exec())时都能很好地工作。POSIX已标准化posix_spawn()来代替后者的后两种更现代的用法vfork()
  • posix_spawn()等效于fork()/execve(),也允许两者之间的fd玩弄。应该代替fork()/execve(),主要用于非MMU平台。
  • pthread_create() 创建一个新线程。
  • clone()是特定于Linux的调用,可用于实现从fork()到的任何内容pthread_create()。它提供了很多控制权。受到启发rfork()
  • rfork()是计划9专用的通话。它应该是一个通用调用,它允许在整个进程和线程之间进行多个程度的共享。

2
感谢您提供比实际需要的更多的信息,它帮助我节省了时间
Neeraj

5
计划9就是这样一个挑逗。
JJ

1
对于那些不记得MMU含义的人:“内存管理单元”- 在Wikipedia上
mgarey

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

2
请注意,BSD和POSIX的要求vfork太弱,以至于不能将其vfork作为同义词fork(并且POSIX.1-2008 vfork完全从规范中删除)。如果您碰巧在与它们同义的系统上测试您的代码(例如,除NetBSD之外的大多数4.4后BSD,2.2.2-pre6之前的Linux内核等),即使您违反vfork合同,它也可能起作用,然后爆炸如果您在其他地方运行它。一些使用它进行仿真的人fork(例如OpenBSD)仍然保证父级直到子级exec或级之前都不会恢复运行_exit。可笑的是不可移植。
ShadowRanger

2
关于第三点的最后一句话:我在Linux上使用strace注意到,虽然fork()的glibc包装器确实调用了克隆syscall,而vfork()的包装器确实调用了vfork系统调用
ilstam

7

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,虚拟内存恰好是由父子共同决定的-正因为如此,父子不能同时被唤醒,因为它们会相互影响。因此,父亲将在“ 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页表。


-4

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


3
错误。vfork()可以实现为fork()
ninjalj 2013年

在AnyFork()之后,没有定义谁运行第一个父/子。
AjayKumarBasuthkar 2014年

5
@Raj:如果您认为在分叉之后有一个隐含的序列顺序概念,那么您在概念上会有一些误解。分叉会创建一个进程,然后将控制权交还给这两个进程(每个进程返回一个不同的值pid)-操作系统可以安排新进程并行运行(如果这样的话)(例如,多个处理器)。如果由于某种原因您需要这些进程以特定的串行顺序执行,则需要分叉不提供的其他同步;坦白说,您可能甚至根本不需要叉子。
Andon M. Coleman

其实@AjayKumarBasuthkar和@ninjalj,你们都错了。使用vfork(),孩子先跑。在手册页中。父母的处决被中止,直到孩子死亡或死亡exec。然后ninjalj查找内核源代码。之所以无法实现,vfork()fork()因为它们将不同的参数传递给do_fork()内核。但是,您可以vfork使用clonesyscall来实现
Zac Wimer

@ZacWimer:请参见ShadowRanger对另一个答案的评论stackoverflow.com/questions/4856255/… 旧的Linux 确实使它们同步化,显然是NetBSD以外的BSD(它倾向于移植到许多非MMU系统上)。从Linux手册中:在4.4BSD中,它被
ninjalj
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.