在类 Unix 系统中创建新进程的可移植方法是使用 fork(2) 调用。BSD 引入了一个变体,称为 vfork(2),作为一种优化技术。在 vfork(2) 中,与 fork(2) 不同,子进程借用父进程的内存和控制线程,直到调用 execve(2V) 或发生退出;当子进程正在使用其资源时,父进程会被挂起。其原理是,在旧的 BSD 系统中,fork(2) 实际上会导致内存被复制,而 vfork(2) 则不会。Linux 从未遇到过这个问题;由于 Linux 在内部使用了写时复制语义,Linux 仅在页面发生更改时才复制页面(实际上,仍然有一些表需要复制;在大多数情况下,它们的开销并不显着)。然而,由于某些程序依赖于 vfork(2),最近 Linux 实现了 BSD vfork(2) 语义(以前 vfork(2) 曾是 fork(2) 的别名)。
vfork(2) 存在许多问题。从可移植性的角度来看,vfork(2) 的问题在于,进程要做到不干扰其父进程实际上相当棘手,尤其是在高级语言中。“不干扰”的要求适用于生成的实际机器代码,许多编译器会生成隐藏的临时变量和其他代码结构,从而导致意外的干扰。结果是:使用 vfork(2) 的程序在代码更改甚至编译器版本更改时很容易失败。
对于安全程序,在 Linux 系统上情况变得更糟,因为 Linux(至少 2.2 版本到 2.2.17 版本)在 vfork() 的实现中容易受到竞争条件的影响。如果特权进程在 Linux 中使用 vfork(2)/execve(2) 对来执行用户命令,则当子进程已作为用户的 UID 运行时,但在尚未进入 execve(2) 时,会存在竞争条件。用户可能能够向此进程发送信号,包括 SIGSTOP。由于 vfork(2) 的语义,特权父进程也会被阻止。因此,非特权进程可能导致特权进程停止,从而导致拒绝服务特权进程的服务。至少 FreeBSD 和 OpenBSD 具有专门处理这种情况的代码,据我所知,它们不会受到此问题的影响。感谢 Solar Designer,他在 2000 年 10 月 7 日在 ``security-audit'' 邮件列表中指出并记录了 Linux 中的这个问题。
关于 vfork(2) 的底线很简单:不要在你的程序中使用 vfork(2)。这应该不难做到;vfork(2) 的主要用途是支持需要 vfork 语义的旧程序。