![Linux 中 fork() 会立即复制整个进程堆吗?](https://linux22.com/image/54404/Linux%20%E4%B8%AD%20fork()%20%E4%BC%9A%E7%AB%8B%E5%8D%B3%E5%A4%8D%E5%88%B6%E6%95%B4%E4%B8%AA%E8%BF%9B%E7%A8%8B%E5%A0%86%E5%90%97%EF%BC%9F.png)
Afork()
系统调用从正在运行的进程克隆子进程。除了 PID 之外,这两个进程完全相同。
当然,如果进程只是从堆中读取而不是写入,则复制堆将浪费大量内存。
整个进程堆都被复制了吗?是否以只有写入才会触发堆复制的方式进行了优化?
答案1
Linux 内核在fork()
调用时确实实现了 Copy-on-Write。执行系统调用时,父级和子级共享的页面将被标记为只读。
如果在只读页上执行写入操作,则会复制该页,因为两个进程之间的内存不再相同。因此,如果仅执行读取操作,则根本不会复制页面。
答案2
这整体offork()
是使用mmap/copy on write实现的。
这不仅影响堆,还影响共享库、堆栈、BSS 区域。
顺便说一句,这意味着 fork 是一个极其轻量级的操作,直到生成的 2 个进程(父进程和子进程)实际开始写入内存范围。此功能是分叉炸弹致命性的一个主要因素 - 在内核因页面复制和区分而过载之前,您最终会遇到太多进程。
您将很难在现代操作系统中找到内核执行硬拷贝的操作示例(设备驱动程序是例外) - 使用虚拟机功能要容易得多且效率更高。
Evenexecve()
本质上是“请 mmap 二进制文件/ld.so/诸如此类的东西,然后执行”——VM 负责将进程实际加载到 RAM 并执行。本地未初始化的变量最终从“零页”进行映射 - 包含零的特殊只读写时复制页,本地初始化变量最终从二进制文件本身进行映射(再次是写时复制), ETC。
答案3
Linux 提供写时复制。fork
创建新进程时,分配的页面被标记为只读并在父进程和子进程之间共享。当其中任何一个尝试修改页面时,就会生成页面错误,从而导致复制页面并适当调整页表。