linux中fork()函数的小问题
问题描述:分析下列代码,分别能产生多少a
// 1
for(int i=0; i<3; i++){
printf("a\n");
fork();
}// 2
for(int i=0; i<3; i++){
fork();
printf("a\n");
}// 3
for(int i=0; i<3; i++){
fork();
printf("a");
}
fflush(stdout);// 4
for(int i=0; i<3; i++){
printf("a");
fork();
}
分析:
【问题1】第0次循环:此时只有p0主进程,直接输出a,然后执行fork();产生一个子进程p1;现在有一个a
第1次循环:此时有两个进程p0,p1;都输出一个a ,共两个a;然后执行fork();分别产生p2,p3,两个子进程;目前终端输出三个a,有四个进程p0,p1,p2,p3;
第2次循环:此时四个进程各输出一个 a ;随后产生四个进程,程序结束;终端上一共有1+2+4 = 7 个a。
【问题2】第0次循环:此时只有p0主进程,执行fork(),生成子进程p1,两个进程各自在终端输出一个a,此时终端共两个a;
第1次循环:此时有p0,p1进程,执行fork(),生成子进程p2,p3,共四个进程,各自输出一个a,此时终端共有 2+4 = 6 个a;
第2次循环:此时有p0,p1,p2,p3进程,执行fork(),生成四个子进程,共八个子进程,各自在终端输出8个子进程,此时共有 2+ 4+8 = 14个a;
但是在终端上出现如下情况,原因为各进程间结束时间不一致,当主进程结束时,部分子进程还未结束,会继续向终端输出a;
此时只要在for循环后加sleep();等待进程全结束即可得到规整的输出:
void test2(){12 printf("[test2] 预测有 14个\n");13 for(int i=0;i<3;i++){14 fork();15 printf("[test2] : a\n");16 }17 18 sleep(1); 19 }
【问题3】(注:本题以及问题4涉及缓存相关知识)
第0次循环:主进程p0执行fork()后产生子进程p1,随后执行满缓存输出,此时将a输出当进程p0、p1的缓冲区中,并不直接输出到终端(后两次循环也是一样);
第1次循环:p0,p1执行fork()后产生p2,p3子进程,随后执行printf(),将a输出到各进程的缓冲区中,此时p0,p1,p2,p3中的缓冲区都存在两个但还未输出;
第2次循环:同理第1次循环,会产生四个子进程,共八个进程,随后的printf()在各自的进程缓存空间中输出a,当进程结束时,标准输出会刷新缓存,将每个进程中的三个a输出到终端;程序结束后,终端上会显示 3*8 = 24个 a;
若出现输出在命令行之后的情况,原因同问题2,各个进程结束时间不一致导致,使用sleep()就可以解决。
【4】
第0次循环:主进程p0执行printf()后并未直接输出,而是将a输出到p0的缓存中。随后执行fork(),产生的子进程p1会复制p0的用户态地址空间;这里有个小知识点fork()采用写时复制,在随后对共享页中i++时候才真正复制p0的栈堆数据等,此时才将p0中的缓存复制一份,这是区别与问题3的;问题3是在各自缓存中加了一个a,问题4是在父进程缓存中先加了一个a,随后复制到各子进程(后两次不作过多赘述,fork()写时复制策略放在最后);此时p0,p1缓存中各有一个a;
第1次循环:p0,p1执行printf(),将a输出到各进程的缓冲区中,缓存中各自有两个a,执行fork(),产生p2,p3,此时p0,p1,p2,p3中的缓冲区都存在两个a但还未输出;
第2次循环:同理上次循环,会产生四个子进程,共八个进程,并且在fork()前的printf()在各自的进程缓存空间中输出a,各个进程缓存中存在3个a,当进程结束时,标准输出会刷新缓存,将每个进程中的三个a输出到终端;程序结束后,终端上会显示 3*8 = 24个 a;
写时复制
为了减少数据复制的开销, 优化内存管理, fork采用是写时复制(Copy-On-Write,简称COW)的策略。
- 在fork()执行时,操作系统并不立即复制父进程的整个内存空间给子进程。操作系统使父进程和子进程暂时共享相同的物理内存页。
- 这些共享的页面在内存中被标记为只读。如果父进程或子进程尝试写入这些共享的页面(以页为单位),操作系统会为发起写操作的进程(父进程或子进程)分配一个新的物理内存页, 并复制数据到这个页。
写时复制机制确保只有在必要时才复制数据页,这极大地减少了内存使用和提高了效率。