VFORK - Linux手册页
Linux程序员手册 第2部分
更新日期: 2017-09-15
名称
vfork-创建子进程并阻止父进程
语法
#包括
#包括
pid_t vfork(无效);
glibc的功能测试宏要求(请参阅feature_test_macros(7)):
vfork():
- Since glibc 2.12:
(_XOPEN_SOURCE >= 500) && ! (_POSIX_C_SOURCE >= 200809L) || /* Since glibc 2.19: */ _DEFAULT_SOURCE || /* Glibc versions <= 2.19: */ _BSD_SOURCE
- Before glibc 2.12:
- _BSD_SOURCE || _XOPEN_SOURCE>= 500
说明
Standard description
(来自POSIX.1)vfork()函数具有与fork(2)相同的作用,不同之处在于,如果由vfork()创建的进程修改了除用于存储数据的pid_t类型变量之外的任何数据,则该行为未定义。从vfork()返回值,或者从调用vfork()的函数返回,或者在成功调用_exit(2)或exec(3)系列函数之一之前调用任何其他函数。
Linux description
就像fork(2)一样,vfork()创建了调用过程的子进程。有关详细信息以及返回值和错误,请参见fork(2)。
vfork()是clone(2)的特例。它用于创建新流程而无需复制父流程的页表。在对性能敏感的应用程序中,创建子项然后立即发出execve(2)可能有用。
vfork()与fork(2)的不同之处在于,调用线程被挂起直到子级终止(正常情况下是通过调用_exit(2)或在传递致命信号后异常终止),或者是它对execve( 2)。在此之前,子级与父级共享所有内存,包括堆栈。子级不得从当前函数返回或调用exit(3)(这将具有调用由父进程建立的退出处理程序并刷新父级的stdio(3)缓冲区的效果),但可以调用_exit(2)。
与fork(2)一样,由vfork()创建的子进程继承了调用者的各种进程属性(例如,文件描述符,信号处理和当前工作目录)的副本;如上所述,vfork()调用仅在虚拟地址空间的处理方面有所不同。
在子级释放父级的内存之后(即在子级终止或调用execve(2)之后),发送给父级的信号会到达。
Historic description
在Linux下,fork(2)是使用写时复制页面实现的,因此fork(2)唯一的代价就是复制父级的页表并为子级创建唯一的任务结构所需的时间和内存。 。但是,在糟糕的过去,fork(2)通常需要不必要地对调用方的数据空间进行完整复制,因为通常在此之后立即执行exec(3)。因此,为了提高效率,BSD引入了vfork()系统调用,该系统调用没有完全复制父进程的地址空间,而是借用了父进程的内存和控制线程,直到发生对execve(2)的调用或退出。当孩子使用其资源时,父进程被暂停。 vfork()的使用非常棘手:例如,在父进程中不修改数据取决于知道哪些变量保存在寄存器中。
遵循规范
4.3BSD; POSIX.1-2001(但标记为过时)。 POSIX.1-2008删除了vfork()的规范。
这些标准对vfork()的要求要弱于对fork(2)的要求,因此这两者是同义的实现是合规的。特别是,程序员不能依赖于父代保持阻塞状态,直到子代终止或调用execve(2),并且不能依赖于有关共享内存的任何特定行为。
备注
有人认为vfork()的语义是体系结构缺陷,因此4.2BSD手册页指出:"实施适当的系统共享机制后,将消除此系统调用。用户不应依赖vfork()的内存共享语义因为在这种情况下,它将成为fork(2)的同义词。"但是,即使现代内存管理硬件已减小了fork(2)和vfork()之间的性能差异,Linux和其他系统仍保留vfork()的原因仍然有多种原因:
- *
- 一些对性能至关重要的应用程序需要vfork()赋予的较小的性能优势。
- *
- vfork()可以在缺少内存管理单元(MMU)的系统上实现,而fork(2)不能在此类系统上实现。 (POSIX.1-2008从标准中删除了vfork(); posix_spawn(3)函数的POSIX基本原理指出,该函数提供的功能与fork(2)+ exec(3)等效)旨在缺少MMU的系统。)
- *
- 在内存受限制的系统上,vfork()避免了临时提交内存(请参见proc(5)中的/ proc / sys / vm / overcommit_memory的描述)以执行新程序。 (这在大型父进程希望在子进程中执行小型帮助程序的情况下特别有益。)相比之下,在这种情况下使用fork(2)需要提交等于父进程大小的内存量。 (如果严格执行过量使用)或过度使用内存,则存在进程被内存不足(OOM)杀手终止的风险。
Caveats
子进程应注意不要以意外的方式修改内存,因为一旦子进程终止或执行另一个程序,父进程就会看到这种更改。在这方面,信号处理程序可能会特别成问题:如果在vfork()的子级中调用的信号处理程序更改了内存,则这些更改可能导致从父进程的角度来看不一致的进程状态(例如,内存更改会在父级中可见,但是对打开文件描述符状态的更改将不可见)。
在多线程进程中调用vfork()时,只有调用线程被挂起,直到子级终止或执行新程序。这意味着孩子正在与其他正在运行的代码共享地址空间。如果父进程中的另一个线程更改了凭据(使用setuid(2)或类似的命令),则可能会很危险,因为现在在同一地址空间中运行着两个具有不同特权级别的进程。作为危险的示例,假设以root用户身份运行的多线程程序使用vfork()创建了一个子代。在vfork()之后,父进程中的线程将该进程拖放给非特权用户,以便运行一些不受信任的代码(例如,可能通过dlopen(3)打开的插件)。在这种情况下,如果父进程使用mmap(2)映射将由特权子进程执行的代码,则可能发生攻击。
Linux notes
当使用NPTL线程库的多线程程序调用vfork()时,不会调用使用pthread_atfork(3)建立的分叉处理程序。在这种情况下,使用LinuxThreads线程库在程序中调用Fork处理程序。 (有关Linux线程库的描述,请参见pthreads(7)。)
调用vfork()等效于使用指定为以下标志的调用clone(2):
CLONE_VM CLONE_VFORK | SIGCHLD
History
vfork()系统调用出现在3.0BSD中。在4.4BSD中,它被称为fork(2)的代名词,但NetBSD再次引入了它。请参阅在Linux中,直到2.2.0-pre6左右,它等效于fork(2)。从2.2.0-pre9开始(在i386上,在其他体系结构上稍晚一些),它是一个独立的系统调用。在glibc 2.0.112中添加了支持。
BUGS
信号处理的细节晦涩难懂,并且在系统之间有所不同。 BSD手册页指出:"为避免可能的死锁情况,永远不会向处于vfork()中间的子进程发送SIGTTOU或SIGTTIN信号;相反,允许输出或ioctl,而输入尝试将导致最终的-文件指示。"
出版信息
这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/。