#include <unistd.h>
pid_t fork(void);
由fork创建的新进程为子进程。fork函数被调用一次,但返回两次,两次返回的唯一区别是子进程的返回值为0,而父进程的返回值则是新子进程的进程ID。将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID。fork使子进程得到返回值为0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得父进程的进程ID(进程ID 0总是由内核交换进程使用,所以一个子进程的进程ID不可能是0)
子进程是父进程的副本。子进程获得父进程数据空间、堆栈和栈的副本。父子进程并不共享这些存储空间部分,父子进程共享正文段。
由于在fork之后经常跟随exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全复制。而是使用写时复制(Copy-On-Write,COW)技术。这些区域由父、子进程共享,如果父、子进程中的任何一个试图修改这些区域,则内核只为修改区域的那块内存(通常是一个页)制作一个副本。
fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中。父、子进程的每个相同的打开描述符共享一个文件表项。所以父子进程对文件的操作需要同步(这种情况通常也不会出现,因为一般父进程会等待子进程运行完毕,或者父子进程执行不同的程序段,即子进程使用exec)。
子进程使用exec函数时,系统把代码段换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段和堆栈段,唯一留下的就是进程ID,也就是说,对系统而言,还是同一个进程,不过已使另外一个程序了。
#include <unistd.h>
pid_t vfork(void);
vfork用于创建一个新进程,而该新进程的目的是exec一个新程序。vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就不会存访该地址空间。
相反,在子进程调用exec或exit之前,它在父进程的空间中运行。这种优化方式在某些UNIX的页式虚拟存储器实现中提高了效率(相比使用COW技术)。
vfork和fork的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后,父进程才可能被调度运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。)