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

use*_*033 190 linux fork clone process exec

我希望在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,创建一个新进程.与fork不同,这些调用允许子进程与调用进程共享其执行上下文的一部分,例如内存空间,文件描述符表和信号处理程序表.

使用clone创建子进程时,它将执行函数应用程序fn(arg).(这与fork不同,fork从原始fork调用开始继续执行.)fn参数是指向子进程在执行开始时调用的函数的指针.arg参数传递给fn函数.

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

获得的信息形式:

感谢您抽时间阅读 !:)

Jav*_*ier 149

  • vfork()是一个过时的优化.在良好的内存管理之前,fork()制作了父级内存的完整副本,因此它非常昂贵.因为在许多情况下a fork()后面exec()会丢弃当前的内存映射并创建一个新内存映射,这是一笔不必要的费用.如今,fork()不复制记忆; 它只是设置为"写入时复制",所以fork()+ exec()vfork()+ 一样高效exec().

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

  • `vfork`避免了临时编写更多内存的需要,因此可以执行`exec`,它仍然比`fork`更有效,即使不是那么高的程度.因此,人们可以避免不得不过度使用内存,这样一个庞大的程序可以产生子进程.因此,不仅仅是性能提升,而且可能使其完全可行. (21认同)
  • 相关:[fork()内部调用clone()是真的吗?](http://stackoverflow.com/q/18904292/1937994) (10认同)
  • 它必须复制所有页面表,在*两个进程中设置所有可写内存copy-on-write*,刷新TLB,然后它必须在`exec上将所有更改还原到父级(并再次刷新TLB) `. (4认同)
  • 实际上,我亲眼目睹了当你的RSS很大时fork()的价格是多么便宜.我认为这是因为内核仍然需要复制所有页表. (3认同)
  • 还有关:http://unix.stackexchange.com/questions/87551/which-file-in-kernel-specifies-fork-vfork-to-use-sys-clone-system-call (2认同)
  • vfork在cygwin中仍然有用(一个模拟dll的内核,运行在微软的Windows之上).cygwin无法实现高效的fork,因为底层操作系统没有. (2认同)

nin*_*alj 77

  • 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()是Plan-9特定的电话.它应该是一个通用调用,允许在完整进程和线程之间进行多度共享.

  • 计划9就是这样的挑逗. (3认同)
  • 感谢您添加了比实际要求更多的信息,这有助于我节省时间 (2认同)
  • 对于那些不记得 MMU 含义的人:“内存管理单元” - 进一步阅读 [维基百科](https://en.wikipedia.org/wiki/Memory_management_unit) (2认同)

Zar*_*trA 40

  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()系统调用形成了一个称为"fork-and-exec"的经典UNIX进程管理模型.

  • 这是一个比被接受的答案更好的答案. (3认同)
  • 请注意,`vfork`的BSD和POSIX要求非常弱,以使`vfork`成为`fork`的同义词是合法的(并且POSIX.1-2008完全从规范中删除`vfork`).如果你碰巧在一个同义它们的系统上测试你的代码(例如除了NetBSD之外的大多数4.4之后的BSD,2.2.0之前的pre-Linux内核等),即使你违反了'vfork`合同,它也可以工作,如果你在其他地方运行它会爆炸.一些使用`fork`(例如OpenBSD)模拟它的人仍然保证父母在孩子`exec`s或`_exit`s之前不会恢复运行.这是荒谬的非便携式. (2认同)
  • 关于第三点的最后一句话:我在 Linux 上使用 strace 注意到,虽然 fork() 的 glibc 包装器确实调用了克隆系统调用,但 vfork() 的包装器调用了 vfork 系统调用 (2认同)

use*_*800 6

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.
Run Code Online (Sandbox Code Playgroud)

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

对于vfork来说,虚拟记忆完全是由孩子和父亲 - 正因为如此,父亲和孩子不能同时醒来,因为他们会相互影响.所以父亲将在"do_fork()"结束时睡觉,并在孩子调用exit()或execve()时唤醒,因为它将拥有新的页面表.这是父亲睡觉的代码(在do_fork()中).

if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;
Run Code Online (Sandbox Code Playgroud)

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

up(tsk->p_opptr->vfork_sem);
Run Code Online (Sandbox Code Playgroud)

对于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页表.