fork(),vfork(),写时复制(Copy-On-Write, COW)

fork()与写时复制(Copy-On-Write, COW)

概念

fork函数:

  • 返回子进程的pid给父进程:父进程没有函数可以获取所有子进程的pid。
  • 返回0给子进程:子进程可以通过getppid获取父进程的pid;而PID=0是内核交换进程,不可能是子进程,因此返回0作为标识,与父进程区分。

COW技术:

  • Linux的fork()使用写时复制技术,父子进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间(也就是说,两者的虚拟空间不同,但其对应的物理空间相同)。
  • fork()的实际开销就是复制父进程的页表以及给子进程创建惟一的进程描述符。
  • 父子任一进程试图修改数据段、堆栈,才会为修改的区域(通常为虚存中一页)制作副本。

关于持有副本的是父进程还是子进程,参考:https://zhoujianshi.github.io/articles/2017/fork()%E5%90%8Ecopy%20on%20write%E7%9A%84%E4%B8%80%E4%BA%9B%E7%89%B9%E6%80%A7/index.html

COW分析

由于fork()的子进程经常调用exec()函数,且exec()会用新的程序替换当前的正文段、数据段、堆栈(也就是分配新的物理空间),此时子进程不需要父进程内存的副本。因此采用COW技术提高了效率。

其他
  • fork之后内核会通过将子进程放在队列的前面(取决于调度算法),以让子进程先执行,以免父进程执行导致写时复制,而后子进程执行exec系统调用,因无意义的复制而造成效率的下降。
  • vfork():
    • 子进程目的是exec一个新程序
    • 直接共享了父进程的虚拟空间
    • 保证子进程先运行,直到调用exec或exit,父进程才可能被调度。
  • stl::string:有些版本的stl采用浅拷贝,只拷贝指针,只有在内容被修改的时候, 才真正分配了新的内存并copy。也就是COW技术。
  • 父子进程共享文件偏移量
<think>嗯,用户问的是在Ubuntu中forkvfork的区别。我需要先回忆一下这两个系统调用的基本概念。fork是创建进程的经典方法,而vfork我记得是为了优化而设计的,可能和exec一起使用的候更高效。 首先,fork复制父进程的地址空间,生成一个子进程。这候如果父进程很大,复制整个内存空间会很耗资源。而vfork可能不会立即复制,而是让子进程共享父进程的地址空间,直到子进程调用exec或者exit,这样可以节省资源。但这样的话,子进程如果修改数据可能会影响父进程,所以vfork之后子进程应该立即执行exec,否则会有问题。 然后,执行顺序的问题。fork之后父进程和子进程的执行顺序是不确定的,取决于调度器。但vfork会保证子进程先运行,父进程会被挂起,直到子进程调用exec或exit。这样避免了在共享地址空间出现竞争条件。 还有,性能方面。vfork因为避免了复制页表等操作,应该比fork更快,特别是在处理大进程的候。不过现代操作系统可能有复制的优化,使得fork的效率也不错,可能vfork的使用场景变少了? 需要确认一下复制Copy-On-Write)的情况。fork使用COW,只有在才会复制页面,所以实际开销可能不大。而vfork可能更早地共享,并且要求子进程立即exec,这样就不需要复制,直接替换新的程序映像,避免了不必要的复制。 另外,安全性方面,vfork因为共享地址空间,如果子进程不按预期操作,比如修改了变量而没有调用exec,会导致父进程出现不可预料的行为。所以vfork的使用需要特别小心,而fork则相对安全。 可能用户是在学习进程创建相关的系统调用,或者遇到性能问题需要优化。他们可能是在开发需要频繁创建子进程的程序,比如服务器或者shell,这候了解这两个的区别可以帮助他们选择合适的函数。 需要总结一下两者的主要区别:地址空间的处理方式、执行顺序、性能差异、使用场景。还要提到在现代系统中,由于forkCOW优化,vfork可能已经不那么必要,但在某些特定情况下还是有用。 最后,确保回答结构清晰,分点说明区别,并给出使用建议,帮助用户理解何选择哪一个。</think>在 Ubuntu(基于 Linux 内核)中,`fork` 和 `vfork` 都是用于创建新进程的系统调用,但设计目标和行为有显著差异。以下是逐步对比分析: --- ### 1. **地址空间复制机制** - **`fork()`** - 创建子进程,**完整复制父进程的地址空间**(代码段、数据段、堆栈等)。 - 实际通过 **复制Copy-On-Write, COW)** 优化:初始父子进程共享物理内存,仅当某进程尝试修改内存,才会复制对应的内存页。 - 代码示例: $$ \text{父进程 PID}=A \xrightarrow{\text{fork()}} \text{子进程 PID}=B \ (\text{独立内存}) $$ - **`vfork()`** - **不复制父进程的地址空间**,子进程直接共享父进程的地址空间。 - **设计目标**:避免 `fork()` 的内存复制开销,专门为后续立即调用 `exec()` 的场景优化。 - **风险**:若子进程修改共享内存,可能破坏父进程的状态。 --- ### 2. **执行顺序保证** - **`fork()`** - 父子进程的执行顺序**不确定**,由调度器决定。 - 需通过返回值区分父子进程(父进程返回子进程 PID,子进程返回 0)。 - **`vfork()`** - **强制保证子进程先执行**,父进程会被挂起,直到子进程调用 `exec()` 或 `_exit()`。 - 若子进程未调用上述函数直接返回,可能导致父进程内存损坏。 --- ### 3. **性能差异** - **`fork()`** - 虽然通过 COW 优化了内存复制,但**仍需复制页表等元数据**,开销相对较高。 - **`vfork()`** - 因无需复制内存和页表,**创建速度更快**,适合需要频繁创建短生命周期子进程的场景。 --- ### 4. **使用场景** - **`fork()` 适用场景** - 需要父子进程**独立运行**且修改各自内存(如服务器进程、Shell 命令执行)。 - 代码示例: ```c pid_t pid = fork(); if (pid == 0) { // 子进程代码 } else { // 父进程代码 } ``` - **`vfork()` 适用场景** - 子进程**立即调用 `exec()`** 加载新程序(如执行 `ls`、`gcc` 等外部命令)。 - 代码示例: ```c pid_t pid = vfork(); if (pid == 0) { execlp("ls", "ls", NULL); _exit(1); // 必须用 _exit() 避免刷新父进程的 I/O 缓冲区 } else { // 父进程恢复执行 } ``` --- ### 5. **现代系统中的演变** - **`fork()` 优化**:现代 Linux 的 `fork()` 已通过 COW 大幅优化,`vfork()` 的优势不再明显。 - **`vfork()` 现状**:多数情况下应优先使用 `fork()`,仅在高度关注性能且确保立即调用 `exec()` 使用 `vfork()`。 --- ### 总结对比表 | 特性 | `fork()` | `vfork()` | |---------------------|---------------------------|---------------------------| | 地址空间复制 | COW(延迟复制) | 不复制(直接共享) | | 执行顺序 | 不确定 | 子进程先执行 | | 性能开销 | 较低(因 COW) | 极低 | | 安全性 | 高(内存隔离) | 低(共享内存易冲突) | | 典型用途 | 通用子进程创建 | 紧接 `exec()` 的场景 | 实际开发中,**优先选择 `fork()`**,仅在明确需要优化且能确保子进程行为使用 `vfork()`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值