fork函数的理解1

一、一个进程的构成

   一个进程由正文段(即代码段)、用户数据段以及系统数据段。

其中系统数据段又称进程控制块(PCB),是给操作系统进行调度用的。系统数据段中存放了关于这个进程的:PID 、PPID、优先级、占用的资源、该进程的状态等等。

fork函数用于进程创建。

看下面一段代码

   1:  #include <stdio.h>
   2:  #include <unistd.h>
   3:  #include <stdlib.h>
   4:  #include <sys/types.h>
   5:   
   6:   
   7:  int main(void)
   8:  {
   9:      int a = 0;
  10:      pid_t pid;
  11:   
  12:   
  13:      if((pid = fork()) == -1)
  14:      {
  15:          printf("fork error!\n");
  16:          exit(-1);
  17:      }
  18:   
  19:      if(pid == 0)
  20:      {
  21:          a++;
  22:          printf("child result= %d, pid=%d, ppid=%d, a=%d, &a=%p\n",pid,getpid(),getppid(),a,&a);
  23:      }
  24:      else
  25:      {
  26:          printf("parent  result= %d, pid=%d, ppid=%d, a=%d, &a=%p\n",pid,getpid(),getppid(),a,&a);
  27:   
  28:      }
  29:      return 0;
  30:  }

比如此刻有一个进程P1刚开始执行该程序,但执行到fork函数,进入fork函数体内,此时程序计数器PC(PC中存放的是返回地址,当返回后会把返回值给了pid这个变量)压栈, fork会创建一个P1的子进程P2,P2同样也是有三部分构成(正文段(即代码段)、用户数据段以及系统数据段),这三段基本上与其父进程完全一样,包括压到栈的PC,不同的是他们的pid以及ppid。P2的ppid是P1的进程号,而P1的ppid就是shell的进程号。可以这么理解,此时在内存中有如下两个同样的部分。

                   父PS1的                                                                                 子PS2的

image

image

                     

执行  pstree  命令后

image

他们的家族关系是:

image

可以看到,P1的父进程是bash。

所以当执行完fork函数后,P1和P2都返回,PC出栈,但是P2并不再执行fork函数和fork函数前面的程序,而是将fork的返回值赋值给变量pid。

看一下输出:

image

二、注意

  • 当子进程先于父进程结束,子进程的状态变为 Z,又称“僵尸态”,即虽然结束了,但是还占有一些资源。子进程的资源回收由其父进程负责。
  • 当父进程先于子进程结束,子进程有init进程负责回收。但是此时子进程不在受shell控制,变为后台进程,假如子进程中有getchar等需要用户输入,getchar会返回-1。

当出现第二种情况时,gnome-terminal输出会有点乱:

image

  • 从输出可以看出,两次输出的a的地址相同,原因:

image

即: 子进程和父进程中的a的虚拟地址相同,但是物理地址不同。其中内存管理单元MMU完成虚拟内存和物理内存的转换。

原文地址:https://www.cnblogs.com/pengdonglin137/p/2943281.html