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行
输入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后和程序结束运行期间,系统进程了任务切换。