White Death
 
        操作系统实验课,老师明确说了考试考fork()和exec族函数,我一直以为自己理解的比较透彻,结果昨天小quiz,考了一道非常简单的题,虽然我做对了,但是我竟然有所犹豫,所以需要巩固一下,刚刚进行了一些实验,发现理解的更进一步了。首先,上个简单的例子:

int main()
{
   int p1,p2;
  
   printf("Once or Twice %d\n",getpid());         // 1
   p1 = fork();
   if(p1 == 0)
   {
      printf("This is the child process \n");
      execl("/bin/ls","ls","-a",NULL);
      printf("Will it be displayed????? \n");            //2
   }
   else
   {
       wait(p1);
       printf("This is the parent \n");
   }
   printf("The end\n");           //3
   return 0;
}


        我发现,1,3都只会显示一次,而2永远不显示,但是讲蓝色高亮的那行execl去掉,2就会显示1次了,而3会显示2次了。说明fork()新创建的子进制只执行从fork开始后面的进程内容(注意,是“只执行”,而不是“只复制”,我们后面会看到),而exec族函数调用后会将所有的进程都覆盖,也就是说不保留父进程的任何信息,相当于脱胎换骨了,所以在那个if里面execl后面写任何代码都不会被执行的。同理,3处的代码,子进程也不好去执行了。
      然后这里就想到一个好玩的程序,如果是在for循环里面调用fork()呢,如下:
int main()
{
    int i,pid1;
    for (i=0; i < 3; i++ )
   {
      pid1 = fork();
      if (pid1 == 0) {
        printf("This is child process! \n");
        //return 0; 
      } else {
        wait(pid1);
        printf("This is parent process!\n");
      }
   }
   return 0;
}
     如果你执行一下这段程序,你就会发现,咦?不应该是显示3对么?怎么这么多?
于是,为了看得清楚:
int main()
{
  int i,pid1;
  for (i=0; i < 3; i++ )
   {
      pid1 = fork();
  
      if (pid1 == 0) {
        printf("This is child process! %d \n",i);
        //return 0; 
      } else {
        wait(pid1);
        printf("This is parent process! %d\n",i);
      }
   }
   return 0;
}

    于是,我们得到如下结果:
Picture
      为什么这么诡异呢?看到0,1,2,2,1...于是我知道了,再第一次循环的时候,子进程并非只复制父进程fork()后面的内容,而且复制了父进程的所有内容,只是它只是从fork()后面执行而已(相当于CS:IP为fork()后一条指令的地址!)
     于是这个例子就不难理解了,i = 0时,那个child开始循环,注意,这里child保存了父进程的所有内容,包括i,所以child执行i = 1,接着 i =2,结束后,开始parent 2(注意:虽然child改变了i,但是这里执行的是copy on write,即在子进程修改前,父进程先另外copy一份i保存起来),parent1(还原child1相对的那个i)....整个过程就像是递归调用似的...
     这样就把fork剖析地更清楚了:每当父进程fork一个子进程时,其实就是将子进程弄个指针过来,指向父进程的内存块,但是,保留了CS:IP,即当前进程的入口地址,然后安装父进程的内存内容进行执行...当需要改变某一内容时,父进程会另外保存这个内容,即copy on write...