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

本文介绍了Linux系统中fork函数的工作原理及写时复制(Copy-On-Write, COW)技术的应用。详细解释了如何利用COW技术提高fork后执行exec函数的效率,并探讨了vfork函数的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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技术。
  • 父子进程共享文件偏移量
在Linux系统中,`fork()`和`vfork()`是两个用于创建新进程的系统调用。它们之间存在显著的区别,这些区别影响着它们的行为、性能以及适用场景。 ### `fork()`函数 `fork()`函数通过复制当前进程来创建一个新的子进程。这个新进程几乎是原始进程的一个副本,包括代码段、数据段、堆栈等。然而,现代Linux内核采用了复制Copy-on-Write, CoW)技术[^2],这意味着实际的数据复制只有当父子进程中任一进程试图修改内存才会发生。这种优化减少了不必要的资源消耗,并提高了效率。 - **行为**:子进程获得父进程的所有内存页面的拷贝,但仅在需要才进行深拷贝。 - **独立性**:子进程与父进程完全隔离,拥有自己的地址空间。 - **执行顺序**:无法保证哪个进程先运行;通常由操作系统调度决定。 - **使用场景**:适合于那些需要完全独立的新进程的情况,尤其是在后续会调用`exec`系列函数加载新的程序到子进程中去的情形。 ### `vfork()`函数 相比之下,`vfork()`的设计目的是为了提高某些特定情况下的效率。它允许子进程共享父进程的地址空间直到子进程调用了`_exit()`或其中一个`exec`家族函数[^3]。在此期间,如果子进程尝试对内存做出任何更改,则可能导致未定义的行为。 - **行为**:子进程不复制父进程的页表项而是直接使用父进程的,因此非常快。 - **独立性**:子进程和父进程共享相同的地址空间,这要求开发者特别小心处理内存操作。 - **执行顺序**:传统上,`vfork()`会挂起父进程直至子进程结束或者调用`exec`/`_exit`,确保子进程优先执行。 - **使用场景**:适用于快速产生一个仅用来执行新程序的子进程的情况,且不需要访问除其自身栈之外的其他内存区域。 ### 应用实例对比 #### 使用`fork()` ```c #include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid = fork(); if (pid < 0) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程 printf("Child process via fork.\n"); } else { // 父进程 printf("Parent process after fork.\n"); } return 0; } ``` #### 使用`vfork()` ```c #include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid = vfork(); if (pid < 0) { perror("vfork"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程 printf("Child process via vfork.\n"); _exit(EXIT_SUCCESS); // 必须使用_exit而不是exit以避免清理操作 } else { // 父进程 printf("Parent process after vfork.\n"); } return 0; } ``` ### 注意事项 尽管`vfork()`可能提供更好的性能,但由于它限制了子进程如何操作内存并且可能导致难以调试的问题,推荐尽可能使用`fork()`。除非是在对速度极为敏感的应用中,否则不应选择`vfork()`[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值