我希望在 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 不同,这些调用允许子进程与调用进程共享其部分执行上下文,例如内存空间、文件描述符表和信号处理程序表。
当使用clone 创建子进程时,它执行函数应用程序fn(arg)。 (这与 fork 不同,在 fork 中,从原始 fork 调用处开始继续在子进程中执行。) fn 参数是一个指向函数的指针,该函数由子进程在其执行开始时调用。 arg 参数被传递给 fn 函数。
当 fn(arg) 函数应用程序返回时,子进程终止。 fn 返回的整数是子进程的退出代码。子进程也可以通过调用 exit(2) 或在收到致命信号后显式终止。
信息获取形式:
fork 和 exec 的区别
http://www.allinterview.com/showanswers/59616.html
http://www.unixguide.net/unix/programming/1.1.2.shtml
http://linux.about.com/library/cmd/blcmdl2_clone.htm
感谢您抽时间阅读 ! :)
fork()
不同,它在 Linux 中,可能是所有 BSD)借用其父级的地址空间。除了调用 execve()
或 _exit()
之外,它所做的任何事情都有很大的可能会弄乱父级。特别是,exit()
调用 atexit()
处理程序和其他“终结器”,例如:它刷新 stdio 流。从 vfork()
子返回可能会(与以前相同的警告)弄乱父堆栈。
fork
系统调用的线程?
vfork() 是一个过时的优化。在良好的内存管理之前,fork() 制作了父内存的完整副本,因此非常昂贵。因为在许多情况下,fork() 后面跟着 exec(),它会丢弃当前的内存映射并创建一个新的映射,所以这是不必要的开销。如今, fork() 不会复制内存。它只是设置为“写入时复制”,因此 fork()+exec() 与 vfork()+exec() 一样有效。
clone() 是 fork() 使用的系统调用。使用一些参数,它创建一个新进程,使用其他参数,它创建一个线程。它们之间的区别只是哪些数据结构(内存空间、处理器状态、堆栈、PID、打开的文件等)是共享的或不共享的。
execve() 将当前可执行映像替换为从可执行文件加载的另一个映像。
fork() 创建一个子进程。
vfork() 是 fork() 的历史优化版本,用于在 fork() 之后直接调用 execve() 时使用。事实证明,它在非 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 特定的调用。它应该是一个通用调用,允许在完整进程和线程之间进行多种程度的共享。
fork() - 创建一个新的子进程,它是父进程的完整副本。子进程和父进程使用不同的虚拟地址空间,最初由相同的内存页填充。然后,随着两个进程的执行,虚拟地址空间开始变得越来越不同,因为操作系统对这两个进程中的任何一个正在写入的内存页面执行惰性复制,并为修改后的页面分配一个独立的副本。每个进程的内存。这种技术称为写时复制 (COW)。 vfork() - 创建一个新的子进程,它是父进程的“快速”副本。与系统调用 fork() 相比,子进程和父进程共享相同的虚拟地址空间。笔记!使用相同的虚拟地址空间,父子都使用相同的堆栈,堆栈指针和指令指针,就像经典的 fork() 一样!为了防止使用相同堆栈的父进程和子进程之间出现不必要的干扰,父进程的执行被冻结,直到子进程调用 exec()(创建新的虚拟地址空间并转换到不同的堆栈)或 _exit() (终止流程执行)。 vfork() 是 fork() 对“fork-and-exec”模型的优化。它可以比 fork() 快 4-5 倍,因为与 fork() 不同(即使考虑到 COW),vfork() 系统调用的实现不包括创建新地址空间(新页面目录的分配和设置)。 clone() - 创建一个新的子进程。该系统调用的各种参数,指定父进程的哪些部分必须复制到子进程中,哪些部分将在它们之间共享。因此,该系统调用可用于创建各种执行实体,从线程开始,到完全独立的进程完成。实际上,clone() 系统调用是用于实现 pthread_create() 和所有 fork() 系统调用家族的基础。 exec() - 重置进程的所有内存,加载和解析指定的可执行二进制文件,设置新堆栈并将控制权传递给加载的可执行文件的入口点。此系统调用永远不会将控制权返回给调用者,并用于将新程序加载到现有进程中。这个系统调用与 fork() 系统调用一起形成了一个经典的 UNIX 进程管理模型,称为“fork-and-exec”。
vfork
的要求非常薄弱,因此将 vfork
设为 fork
的同义词是合法的(并且 POSIX.1-2008 从规范中完全删除了 vfork
)。如果您碰巧在同义词的系统上测试您的代码(例如除了 NetBSD 之外的大多数 4.4 后的 BSD、2.2.0-pre6 之前的 Linux 内核等),即使您违反 vfork
合同,它也可能有效,然后如果你在别处运行它就会爆炸。一些用 fork
模拟它的那些(例如 OpenBSD)仍然保证父级在子级 exec
或 _exit
之前不会恢复运行。这是可笑的不可移植。
fork()、vfork() 和 clone() 都调用 do_fork() 来完成实际工作,但参数不同。
asmlinkage int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.esp, ®s, 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, ®s, 0);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 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 页表。
在 fork() 中,子进程或父进程将根据 cpu 选择执行。但在 vfork() 中,子进程肯定会先执行。孩子终止后,父母将执行。
vfork()
可以实现为 fork()
。
pid
)——如果这样的事情有意义(例如多个处理器)。如果出于某种原因您需要这些进程以特定的串行顺序执行,那么您需要额外的同步,而分叉不提供;坦率地说,你可能一开始就不需要叉子。
vfork()
,孩子先跑。它在手册页中;父母的执行被暂停,直到孩子死亡或exec
。 ninjalj 查找内核源代码。无法将 vfork()
实现为 fork()
,因为它们在内核中将不同的参数传递给 do_fork()
。但是,您可以使用 clone
系统调用实现 vfork
vfork
避免了临时提交更多内存以便执行exec
的需要,并且它仍然比fork
更有效,即使没有那么高的程度。因此,人们可以避免过度使用内存,这样一个庞大的大程序就可以产生一个子进程。因此,不仅仅是性能提升,而且可能使其完全可行。exec
。