fork函数学习1

1.for函数

fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,这个新产生的进程称为子进程。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。需要注意的一点:就是调用fork函数之后,一定是两个进程同时执行的代码段是fork函数之后的代码,而之前的代码已经由父进程执行完毕。

fork函数返回两个值

  • 返回一个大于0的值给父进程 
  • 返回0给子进程
  • 返回其他值说明fork失败了

2.举例

  1 #include <unistd.h>
  2 #include <stdio.h>
  3
  4 int main(void)
  5 {
  6     pid_t pid;
  7     int count = 0;
  8
  9     printf("enter the main function \r\n");    //fork函数之前的,子进程不会运行
 10     pid = fork();       //创建一个子进程
 11     if(0 == pid)
 12     {
 13         printf("this is child process, pid is = %d\n", getpid());   //打印当前运行的进程的进程号
 14     }
 15     else if(0 < pid)
 16     {
 17         printf("this is father process, pid is = %d\n", getpid());    //打印当前运行的进程的进程号
 18     }
 19     else
 20     {
 21         printf("error \n");
 22     }
 23
 24     return 0;
 25 }

编译后运行结果如下:


第一次接触fork函数,不少同学还以为该程序既执行了  0 < pid  也执行了  0 == pid ,这个很明显和if的逻辑不符合。

我们使用gdb工具调试一下,看一下运行结果

第一条将要运行的指令在第7行


扫描二维码关注公众号,回复: 1637089 查看本文章

输入n单步执行,下一条将要运行的指令在第9行


继续输入n单步执行,打印出了第9行的printf里面的内容,同时提示下一条运行的指令位于第10行


继续输入n,单步执行,这次执行后,多打印了一行this is child process, pid is = 2802之后才是即将要运行的第11行。也就是执行完一条 pid = fork()程序就打印出了this is child process, pid is = 2802   该进程的pid号是2802。


这里就用到文章最前面说的fork函数的功能了,调用fork克隆了一个自己,产生一个新的子进程,而这个新的子进程我们并没有进行单步调试,所以很快执行完。且子进程执行的程序是fork函数之后的代码所以没有打印出enter the main function,字符串,因为这个字符串打印是在fork函数前面调用的。要注意的是,fork执行完后对子进程返回0,变量pid值为0,所以才会进入子进程的if(0 == pid)这个分支打印出 child.

借用一位网友用链表的描述。

父进程调用 fork()返回值为新创建的子进行的pid号,即父进程的fpid指向子进程,而子进程没有它的子进程,所以子进程的fpid为NULL

接一下来继续输入n,直到执行完。


会发现我们调试的这个进程,调用fork返回值大于0,且对比两个进程的进程号,会发现,子进程的进程号比父进程要大,因为进程号是从小到大依次增加的。

为了加深对fork函数的理解,参考网上一位网友的博客,对fork()函数进行进一步分析。

该网友的原博客链接为    https://www.cnblogs.com/dongguolei/p/8086346.html

  1 /*
  2  *  fork_test.c
  3  *  version 2
  4  *  Created on: 2010-5-29
  5  *      Author: wangth
  6  */
  7 #include <unistd.h>
  8 #include <stdio.h>
  9
 10 int main(void)
 11 {
 12     int i=0;
 13
 14     printf("i son/pa ppid pid  fpid\n");
 15     //ppid指当前进程的父进程pid
 16     //pid指当前进程的pid,
 17     //fpid指fork返回给当前进程的值
 18     for(i = 0;i < 2;i++)
 19     {
 20         pid_t fpid = fork();
 21         if(0 == fpid)
 22         {
 23             printf("%d child  %4d %4d %4d\n",i,getppid(),getpid(),fpid);
 24         }
 25         else
 26         {
 27             printf("%d parent %4d %4d %4d\n",i,getppid(),getpid(),fpid);
 28         }
 29     }
 30     return 0;
 31 }

有了上面第一个简单的例子。这个程序我们就根据结果进行直接分析。


第一行的 i 表示是for循环的第 i 次。 son/pa表示当前进程运行在子进程还是父进程中。ppid表示当前运行进程的父进程的进程号,pid表示当前进程的进程号,fpid表示fork返回给当前进程的值。

我们把我们当前执行的这个a.out程序称为父进程。

第一步,通过打印信息我们知道,在父进程中,i = 0,之后执行 fpid = fork();该步执行完之后,系统中出现了另一个和当前进程一样的进程,我们称为子进程1,从打印信息可以知道,当前进程即父进程的进程号是2902,子进程1的进程号是2903.当前进程的父进程号为2395.   也即打印出了下面这条信息。

0 parent 2395 2902 2903

他们的关系就像原博客主人用单项链表,表示的那样,2392->2902->2903->NULL

第二步,通过打印信息我们知道,和第一步一样,在父进程中,i = 1,之后执行 fpid = fork();系统出现子进程2,该子进程2的进程号是2904.  也即打印出了下面这条信息

1 parent 2395 2902 2904

之后,父进程执行完两次循环后,整个程序执行完毕,父进程从此刻开始结束运行【从打印结果看,是在2903和2904进程执行之前就结束运行】。

接下来系统开始调用子进程。

系统先调用的pid为2903的子进程1 (也可能先调用子进程2,整个由调度系统决定,我们这个是先调用的1)

第三步,对于pid为2903的子进程1,创建2903的进程时,父进程2392处于第一次循环中i = 0,所以复制产生的2903的i也为0,其fork对他的返回值为0,所以打印出child,又因为其父进程已经结束运行,而每个进程都必须有其父进程,同时进程1是永远不会结束运行,同时也是其他进行的源头,所以对于父进程以已经结束运行的进程,进程1就会是该进程的父进程。

 18     for(i = 0;i < 2;i++)
 19     {
 20         pid_t fpid = fork();   i = 1  fpid = 0
 21         if(0 == fpid)
 22         {
 23             printf("%d child  %4d %4d %4d\n",i,getppid(),getpid(),fpid);
 24         }
 25         else
 26         {
 27             printf("%d parent %4d %4d %4d\n",i,getppid(),getpid(),fpid);
 28         }
 29     }

0 child     1 2903    0

执行了pid为2903的子进程1后,系统调用了pid为2904的子进程2

第四步,对于pid为2904的子进程2,创建2904的进程时,父进程2392处于第一次循环中i = 1,所以复制产生的2904的i也为1,其fork对他的返回值为0,所以打印出child,又因为其父进程已经结束运行进程1就会是该进程的父进程。

 18     for(i = 0;i < 2;i++)
 19     {
 20         pid_t fpid = fork();
 21         if(0 == fpid)    i = 1 fpid = 0
 22         {
 23             printf("%d child  %4d %4d %4d\n",i,getppid(),getpid(),fpid);
 24         }
 25         else
 26         {
 27             printf("%d parent %4d %4d %4d\n",i,getppid(),getpid(),fpid);
 28         }
 29     }

1 child     1 2904    0

同时,当打印出1 child     1 2904    0后,i 进过自加以及为2,故整个程序以及执行完毕。

即pid为2904的进程,在打印出1 child     1 2904    0后继续执行完毕或是在retuen 0和printf之间切换执行其他进程,之后再回来消亡整个进程,其都不会再有任何输出信息。

到这里我们假设2904已经执行完【没执行完也无所谓】,我们用父进程创建的新进程只有2903还在运行,父进程本身和2904都已经执行完。

第五步,pid为2903的子进程1进过第三步的执行后,i++,此时i = 1,该函数执行并调用fork函数,产生一个新的子进程,我们称为进程3,因为其已经产生了子进程,故该进程1已经称为了一个新的进程(进程3)的父进程。用链表发表示为        2904 -> 2903 -> 2905 因为2904已经结束运行。所以表示为

1 -> 2903 -> 2905

 18     for(i = 0;i < 2;i++)
 19     {
 20         pid_t fpid = fork();  i = 1 fpid = 2905
 21         if(0 == fpid)
 22         {
 23             printf("%d child  %4d %4d %4d\n",i,getppid(),getpid(),fpid);
 24         }
 25         else
 26         {
 27             printf("%d parent %4d %4d %4d\n",i,getppid(),getpid(),fpid);
 28         }
 29     }

在该进程打印出1 parent 2395 2892 2894, 后该进程不会再打印任何信息,直至结束运行。

最后一步:pid为2905的子进程3复制了它的父进程创建它时的信息,i = 1 , 返回给它的 fpid为0.

故打印如下。

1 child  2903 2905    0

从打印信息也可以看出,在执行2905进程时,2903进程还未结束运行。即2903在最后一次执行完printf后和程序结束运行期间,系统进程了任务切换。



猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/80441412