关于fork() 简单易懂的总结笔记

fork()笔试题

笔试选择题冒出来一个五连fork()是真的头大,比如说

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(){
        fork();
        fork() || fork() && fork();
        fork();
        printf("1\n");
        return 0;
}

这段代码会生成几个1?

实际上就是问会产生多少个进程,不过printf的位置往前面几行放答案会不一样,和fork()的代码执行原理有关。

fork()的作用

复制一个子进程使得和父进程能做几乎一样的事。fork()执行后会返回2个值,如果是父进程执行时返回子进程pid,子进程自己执行时返回0,创建失败返回-1。
(实际上子进程只执行了父进程的一部分事,至少不会把fork出自己的那句fork()再生成一个子进程,否则整个程序一行fork()就足以死循环)

fork()执行原理剖析

还是刚刚那份代码,我们往fork()前前后后都加点调试用输出,比如

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(){
        printf("checkpoint 1 by pid=%d, parent=%d\n",getpid(),getppid());
        fork();
        printf("checkpoint 2 by pid=%d, parent=%d\n",getpid(),getppid());
        fork() || fork() && fork();
        printf("checkpiont 3 by pid=%d, parent=%d\n",getpid(),getppid());
        fork();
        printf("fork complete by pid=%d, parent=%d\n",getpid(),getppid());
        sleep(1000);//这个sleep比较精髓,后面会讲为什么不可或缺
        return 0;
}

编译输出,可能的输出结果如下:

checkpoint 1 by pid=9453, parent=9063
checkpoint 2 by pid=9453, parent=9063
checkpiont 3 by pid=9453, parent=9063
fork complete by pid=9453, parent=9063
fork complete by pid=9456, parent=9453
checkpiont 3 by pid=9455, parent=9453
checkpoint 2 by pid=9454, parent=9453
checkpiont 3 by pid=9454, parent=9453
fork complete by pid=9454, parent=9453
fork complete by pid=9455, parent=9453
fork complete by pid=9461, parent=9454
fork complete by pid=9460, parent=9455
checkpiont 3 by pid=9458, parent=9455
fork complete by pid=9458, parent=9455
checkpiont 3 by pid=9459, parent=9454
checkpiont 3 by pid=9463, parent=9459
fork complete by pid=9464, parent=9458
fork complete by pid=9463, parent=9459
checkpiont 3 by pid=9462, parent=9459
fork complete by pid=9462, parent=9459
fork complete by pid=9465, parent=9463
fork complete by pid=9466, parent=9462
fork complete by pid=9459, parent=9454
fork complete by pid=9467, parent=9459
checkpiont 3 by pid=9457, parent=9455
fork complete by pid=9457, parent=9455
fork complete by pid=9468, parent=9457

列个表格统计每条printf的出现次数:

pid\ckpt123complete
9063
9453
9454
9455
9456
9457
9458
9459
9460
9461
9462
9463
9464
9465
9466
9467
9468
小计12816

可以看到,这份代码一共创建了16个进程,pid分别是9453-9468(9063是系统进程),那么开头问题的答案就是16。
如果那行printf放到了第1、第2或第3行,答案分别是1、2、8。
那么为啥创建了16个进程?现在我们有每个进程的父进程ID,可以画棵树表达进程创建关系如下:
进程树
这里就是sleep的精髓所在,如果没有sleep,那么父进程在执行完毕后退出,子进程还未结束时,就会成为僵尸进程,由系统进程(9063)接手。那此时的输出,parent这列有可能全是9063,根本分不清进程的创建关系(这是真的)。ps:如果使用root执行该代码,系统进程值固定为1
上面是后话,回到正题
这棵树中9453进程是根节点(9063用来管理9453的不是这个程序创建的),产生了3个进程
9454产生了2个进程,其他以此类推(自己读图)
然后再剖析表格:
执行了全部4行printf的只有根进程9453,表明fork之前的代码只有根进程可以执行,并且根进程执行了全部3行fork(),这3行fork一共创建了3个进程。
我们回想一下子进程执行生成自己的fork()返回值是0的,因此第2行的fork()会存在短路现象,但这里得仔细剖析是哪些fork()被短路而哪些没有。
首先第1和第3行fork()只有单次调用,没有任何逻辑符号干扰,是绝对不会被短路的,一定会被执行,第1行fork()创建了9454,第3行fork()创建了9456。
那么第2行fork()只被执行了1次。
第2行第一个逻辑符号是或,或的短路规则是只要前者为真(非0),后者不执行。那么就说第2行的第1个fork()执行,创建了进程9455。后面两个fork不执行,不创建进程。
在这里插入图片描述
然后往下,执行了3行printf的有根进程9453和第一个子进程9454。按fork()基本原理,第一行fork()会返回0(该行是不会产生新进程的,否则会出现无限循环),由3行输出可以肯定执行了第2行和第3行fork,然后从树上可以看出,这个fork产生了2个进程9459和9461,9461是第3行fork产生的,而9459在第2行产生。可以看出9454执行了完整的第2行fork,因此9459也是第2行的第1个fork产生的,后面两个fork被短路。

在这里插入图片描述
而9455进程由9453的第二行第一个fork()产生,在9455执行第2行第1个fork的时候不会产生新进程并返回0。此时或逻辑的第一个值为0,没有短路,第2个fork得以执行,并产生进程9457。然后9457会会作为9455进程那个fork的返回值,后面与逻辑的第一个值不为0,第3个fork没被短路,因此执行,产生进程9458。然后第3行fork产生进程9460。进程9455不执行前面3行代码,输出第3行和最后一行printf,与表格仍然一致。
在这里插入图片描述最后一个进程9456,由于是9453的最后一行fork()产生的,因此在进程9456中不执行前5行的所有代码,最后一个fork()返回0,执行complete输出,因此不产生任何子进程并只输出一行printf。
在这里插入图片描述
9457以后的进程,按上述推算方法,会发现产生进程的数量和printf执行数量都是符合预期的。
即fork()后的动作是:父进程fork()出那个子进程后一切照常往下跑代码(那个fork()函数返回值是子进程pid);而fork()出来的子进程将从把它生出来的那个fork()开始跑代码(但那个fork()不会再生成子进程,在子进程中返回0),那个fork()以前的代码全部不跑。
一个有趣的规律是这份代码中,所有的进程都打印了complete;所有的叶子进程打且只打了complete;所有的非叶子进程都打印了checkpoint 3。但也是由于第3行fork只有一个fork(),如果也像第二行fork那样加上乱七八糟的逻辑符号就不会有这个规律。

fork延伸

下面这份代码会打印几个1?

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(){
		int i=0;
        for(i=0;i<5;i++)fork();
        printf("1\n");
        return 0;
}

如果把for循环拆成5行fork();,很容易得出答案32。
实际上这份代码就是等价于5行fork(),没有区别。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值