1. fork()

调用fork函数,系统就为一个新的进程准备了三个段。

首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的,对于数据段堆栈段,系统则复制一份给新的进程(实际是COW)。
这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。而如果两个进程要共享什么数据的话,就要使用另一套函数(shmget,shmat,shmdt等)共享内存来操作。

现在,已经是两个进程了,对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零,这样,对于程序,只要判断fork函数的返回值,就知道自己是处于父进程还是子进程中。

事实上,目前大多数的unix系统在实现上并没有作真正的copy。一般的,CPU都是以“页”为单位分配空间的,像Intel的CPU,其一页在通常情况下是4K字节大小,而无论是数据段还是堆栈段都是由许多“页”构成的,fork函数复制这两个段,只是“逻辑”上的,并非“物理”上的
也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别的“页”从物理上也分开,也就是上面提到的COW(copy on write)技术。系统在空间上的开销就可以达到最小。

2. exec()系列函数


一个进程一旦调用exec类函数,它本身就“死亡”了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。

不过exec类函数中有的还允许继承环境变量之类的信息,这个通过exec系列函数中的一部分函数的参数可以得到。

总结

对于fork()

  1. 子进程复制父进程的所有进程内存到其内存地址空间中。父、子进程的 “数据段”,“堆栈段”和“代码段”完全相同,即子进程中的每一个字节都和父进程一样。
  2. 子进程的当前工作目录、umask掩码值和父进程相同,fork()之前父进程打开的文件描述符,在子进程中同样打开,并且都指向相同的文件表项。
  3. 子进程拥有自己的进程ID。

对于exec()

  1. 进程调用exec()后,将在同一块进程内存里用一个新程序来代替调用 exec()的那个进程,新程序代替当前进程映像,当前进程的“数据段”,“堆栈段”和“代码段”背新程序改写。
  2. 新程序会保持调用exec()进程的ID不变。
  3. 调用exec()之前打开的描述字继续打开(好像有什么参数可以令打开的描述字在新程序中关闭)