在进程的创建中, fork()
函数会对父进程的程序文本段、数据段、堆栈区进行严格的复制。不过,如果真的简单地将父进程虚拟内存页复制到新的子进程,就会很浪费时间,因为它需要完成很多事情:为子进程的页表分配页、为子进程的页分配页、初始化子进程的页表、把父进程的页复制到子进程对应的页中。另外一个原因是: fork()
函数之后经常会立刻执行 exec
函数,这就会使用新程序替换进程的代码段,并重新初始化起数据段、堆栈区。这将导致之前对父进程地址空间的复制变成无用功。
针对这种情况, Linux
采用写时复制技术来处理。写时复制技术时一种可以推迟甚至避免复制数据的技术。内核此时并不是复制整个进程空间。而是让父进程与子进程共享统一副本。即使用相同的物理内存空间,子进程的程序文本段、数据段、堆栈区都指向父进程的物理内存空间。也就是说,二者的虚拟内存空间不同,但起对应的物理内存空间是同一个,并且这些分段的页被标记为只读。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理内存空间。这种技术使得对地址空间中的页的复制被推迟到实际发生写入的时候。
类似于 fork()
函数, Linux
也提供了 vfork()
函数为调用进程创建一个新的子进程,一边为程序提供尽可能块的 fork()
功能。
#include <sys/types.h> #include <unistd.h> pid_t vfork(void);点击复制复制失败已复制
vfork()
函数与 fork()
函数不同的是, vfork()
函数不采用写时复制技术。无须为子进程复制虚拟内存页或页表,直到其子进程调用 exec()
函数或 _exit()
函数之前。在此之前,子进程共享父进程的内存,即子进程的操作是在父进程的内存段上进行的。在子进程调用 exec()
函数或 _exit()
函数之前,将暂停执行父进程。系统将先保证子进程先于父进程获得调度以使用 CPU
,而 fork()
函数则无法保证这一点,父子进程都有可能率先获得调度。鉴于以上两点,子进程在对数据段、堆或栈的任何改变将在父进程回复执行时为其所见,因此对子进程的操作需要谨慎,不然可能会因为资源共享的问题,造成对父进程的影响。