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

我想在谷歌上找到这四个之间的区别,我希望在这方面有大量的信息,但这四个调用之间真的没有任何可靠的比较。

我开始尝试编译一种基本的概览,看看这些系统调用之间的差异,下面是我得到的结果。这些信息都正确吗/我遗漏了什么重要的东西吗?

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

新进程(子进程)获得不同的进程ID (PID),并将旧进程(父进程)的PID作为其父PID (PPID)。因为两个进程现在正在运行完全相同的代码,它们可以通过fork的返回代码来区分哪个是哪个——子进程得到0,父进程得到子进程的PID。当然,这只是假设fork调用有效的情况——如果不行,就不会创建子进程,父进程会得到一个错误代码。

Vfork: vfork()fork()之间的基本区别是,当用vfork()创建一个新进程时,父进程会暂时挂起,并且子进程可能会借用父进程的地址空间。这种奇怪的状态一直持续到子进程退出,或者调用execve(),此时父进程 过程仍在继续。< / p >

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

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

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

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

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

获得的信息:

感谢你花时间阅读这篇文章!:)

161733 次浏览
  • vfork()是一个过时的优化。在良好的内存管理之前,fork()会完全复制父节点的内存,所以它的开销相当大。因为在很多情况下fork()后面跟着exec(),它会丢弃当前的内存映射并创建一个新的内存映射,这是不必要的开销。现在,fork()不复制内存;它被简单地设置为“写时复制”,因此fork()+exec()vfork()+exec()一样高效。

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

  • 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特定的调用。它应该是一个通用调用,允许在整个进程和线程之间进行不同程度的共享。
在fork()中

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

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_cleard | CLONE_SYSVSEM);

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

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