ChatGPT解决这个技术问题 Extra ChatGPT

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

我希望在 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

感谢您抽时间阅读 ! :)

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

J
Javier

vfork() 是一个过时的优化。在良好的内存管理之前,fork() 制作了父内存的完整副本,因此非常昂贵。因为在许多情况下,fork() 后面跟着 exec(),它会丢弃当前的内存映射并创建一个新的映射,所以这是不必要的开销。如今, fork() 不会复制内存。它只是设置为“写入时复制”,因此 fork()+exec() 与 vfork()+exec() 一样有效。

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


vfork 避免了临时提交更多内存以便执行 exec 的需要,并且它仍然比 fork 更有效,即使没有那么高的程度。因此,人们可以避免过度使用内存,这样一个庞大的大程序就可以产生一个子进程。因此,不仅仅是性能提升,而且可能使其完全可行。
实际上,当你的 RSS 很大时,我亲眼目睹了 fork() 是多么不便宜。我认为这是因为内核仍然必须复制所有页表。
它必须复制所有页表,在两个进程中设置所有可写内存copy-on-write,刷新TLB,然后它必须将所有更改恢复到父级(并刷新TLB再次)在 exec
vfork 在 cygwin(一个模拟 dll 的内核,在 Microsoft 的 Windows 之上运行)中仍然有用。 cygwin 无法实现高效的分叉,因为底层操作系统没有。
n
ninjalj

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 特定的调用。它应该是一个通用调用,允许在完整进程和线程之间进行多种程度的共享。


感谢您添加比实际要求更多的信息,它帮助我节省了时间
计划 9 就是这样一个挑逗。
对于那些不记得 MMU 是什么意思的人:“内存管理单元”- 进一步阅读 on Wikipedia
Z
ZarathustrA

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”。


请注意,BSD 和 POSIX 对 vfork 的要求非常薄弱,因此将 vfork 设为 fork 的同义词是合法的(并且 POSIX.1-2008 从规范中完全删除了 vfork)。如果您碰巧在同义词的系统上测试您的代码(例如除了 NetBSD 之外的大多数 4.4 后的 BSD、2.2.0-pre6 之前的 Linux 内核等),即使您违反 vfork 合同,它也可能有效,然后如果你在别处运行它就会爆炸。一些用 fork 模拟它的那些(例如 OpenBSD)仍然保证父级在子级 exec_exit 之前不会恢复运行。这是可笑的不可移植。
关于你第三点的最后一句话:我注意到在 Linux 上使用 strace 虽然 fork() 的 glibc 包装器确实调用了克隆系统调用,但 vfork() 的包装器调用了 vfork 系统调用
u
user991800

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 页表。


R
Raj Kannan B.

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


错误的。 vfork() 可以实现为 fork()
在 AnyFork() 之后,没有定义谁运行第一个父/子。
@Raj:如果您认为在分叉后有一个隐含的顺序概念,那么您就会有一些概念上的误解。分叉创建一个 new 进程,然后将控制权返回给两个进程(每个进程返回一个不同的 pid)——如果这样的事情有意义(例如多个处理器)。如果出于某种原因您需要这些进程以特定的串行顺序执行,那么您需要额外的同步,而分叉不提供;坦率地说,你可能一开始就不需要叉子。
实际上@AjayKumarBasuthkar 和@ninjalj,你们都错了。使用 vfork(),孩子先跑。它在手册页中;父母的执行被暂停,直到孩子死亡或exec。 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 左右。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅