一、fork 后的父子进程

由 fork 创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是 0,而父进程的返回值则是新进程(子进程)的进程 id。将子进程 id 返回给父进程的理由是:因为一个进程的子进程可以多于一个,没有一个函数使一个进程可以获得其所有子进程的进程 id。对子进程来说,之所以 fork 返回 0 给它,是因为它随时可以调用 getpid() 来获取自己的 pid;也可以调用 getppid() 来获取父进程的 id。(进程 id 0 总是由交换进程使用,所以一个子进程的进程 id 不可能为 0)。

fork 之后,**操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这 2 个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,子进程拥有父进程当前运行到的位置**(两进程的程序计数器 pc 值相同,也就是说,子进程是从 fork 返回处开始执行的),但有一点不同,如果 fork 成功,子进程中 fork 的返回值是 0,父进程中 fork 的返回值是子进程的进程号,如果 fork 不成功,父进程会返回错误。
可以这样想象,2 个进程一直同时运行,而且步调一致,在 fork 之后,他们分别作不同的工作,也就是分岔了。这也是 fork 为什么叫 fork 的原因

至于那一个最先运行,可能与操作系统(调度算法)有关,而且这个问题在实际应用中并不重要,如果需要父子进程协同,可以通过原语的办法解决。

[cpp]  view plain copy

  1. include <stdio.h>  

  2. #include <unistd.h>  

  3. #include <stdlib.h>  

  4. #include <errno.h>  

  5. int main(void)  

  6. {  

  7.         pid_t pid=fork();  

  8.         if(pid==0)  

  9.         {  

  10.                 int j ;  

  11.                 for(j=0;j<10;j++)  

  12.                 {  

  13.                         printf(“child: %d\n”,j);  

  14.                         sleep(1);  

  15.                 }  

  16.         }  

  17.         else if (pid>0)  

  18.         {  

  19.                 int i;  

  20.                 for(i=0;i<10;i++)  

  21.                 {  

  22.                         printf(“parent: %d\n”,i);  

  23.                         sleep(1);  

  24.                 }  

  25.         }  

  26.         else  

  27.         {  

  28.                 fprintf(stderr,”can’t fork ,error %d\n”,errno);  

  29.                 exit(1);  

  30.         }  

  31.         printf(“This is the end !”);  

  32. }  

二、fork 出的子进程和父进程的继承关系

fork 出来的子进程,基本上除了进程号之外父进程的所有东西都有一份拷贝,基本就意味着不是全部,下面我们要说的是子进程从父进程那里继承了什么东西,什么东西没有继承。还有一点需要注意,子进程得到的只是父进程的拷贝,而不是父进程资源的本身。

由子进程自父进程继承到:     

  1. 进程的资格 (真实(real)/ 有效(effective)/ 已保存(saved) 用户号 (UIDs) 和组号(GIDs))
  2. 环境 (environment)
  3. 堆栈
  4. 内存
  5. 打开文件的描述符 (注意对应的文件的位置由父子进程共享,这会引起含糊情况)
  6. 执行时关闭 (close-on-exec) 标志 (译者注:close-on-exec 标志可通过 fnctl() 对文件描述符设置,POSIX.1 要求所有目录流都必须在 exec 函数调用时关闭。更详细说明,参见《UNIX 环境高级编程》 W. R. Stevens, 1993, 尤晋元等译(以下简称《高级编程》), 3.13 节和 8.9 节)
  7. 信号 (signal) 控制设定
  8. nice 值 (译者注:nice 值由 nice 函数设定,该值表示进程的优先级,数值越小,优先级越高)
    进程调度类别 (scheduler class)(译者注:进程调度类别指进程在系统中被调度时所属的类别,不同类别有不同优先级,根据进程调度类别和 nice 值,进程调度程序可计算出每个进程的全局优先级 (Global process prority),优先级高的进程优先执行)
  9. 进程组号
  10. 对话期 ID(Session ID) (译者注:译文取自《高级编程》,指:进程所属的对话期 (session)ID, 一个对话期包括一个或多个进程组, 更详细说明参见《高级编程》9.5 节)
  11. 当前工作目录
  12. 根目录 (译者注:根目录不一定是 “/”,它可由 chroot 函数改变)
  13. 文件方式创建屏蔽字 (file mode creation mask (umask))(译者注:译文取自《高级编程》,指:创建新文件的缺省屏蔽字)
  14. 资源限制
  15. 控制终端

子进程所独有:

进程号

  1. 不同的父进程号 (译者注:即子进程的父进程号与父进程的父进程号不同, 父进程号可由 getppid 函数得到)
  2. 自己的文件描述符和目录流的拷贝 (译者注:目录流由 opendir 函数创建,因其为顺序读取,顾称 “目录流”)
  3. 子进程不继承父进程的进程,正文 (text), 数据和其它锁定内存 (memory locks)(译者注:锁定内存指被锁定的虚拟内存页,锁定后,4. 不允许内核将其在必要时换出 (page out),详细说明参见《The GNU C Library Reference Manual》 2.2 版, 1999, 3.4.2 节)
  4. 在 tms 结构中的系统时间 (译者注:tms 结构可由 times 函数获得,它保存四个数据用于记录进程使用中央处理器 (CPU:Central Processing Unit) 的时间,包括:用户时间,系统时间, 用户各子进程合计时间,系统各子进程合计时间)
  5. 资源使用 (resource utilizations) 设定为 0
  6. 阻塞信号集初始化为空集 (译者注:原文此处不明确,译文根据 fork 函数手册页稍做修改)
  7. 不继承由 timer_create 函数创建的计时器
  8. 不继承异步输入和输出