几个fork面试题目的验证

来源:互联网 发布:qq做图软件 编辑:程序博客网 时间:2024/04/26 16:17

原题目来源:

 

第一题,计算下面代码理论上总共打印了多少行:(网易2011笔试题)

 

int main(){    int i;    for(i = 0; i<5; i++){        fork();        printf("%d\n",getpid());        fflush(stdout);    }}


问题解答:

问题的答案为62,基本方法为2+4+8+16+32=62,可以认为是每次进程数目加倍,输出的数目实际对应进程的数目,另外原文提出了另一种解决此问题的方法,可以参考原文。

但是如果用程序验证,就不可能得到正确的答案,所以在题目中说明是“理论上“。因为在主进程结束之前,子进程并不一定能够完成执行,为了保证子进程的顺利结束,应当在每一个fork()之后增加wait(),等待子进程结束。验证程序代码如下,

 

int main(){    int i;    int status;    pid_t pid;    for(i = 0; i<5; i++){        fork();        pid=wait(&status);        printf("pid=%d, ppid=%d, child=%d\n",getpid(), getppid(), pid);        fflush(stdout);    }}

可以发现最后输出预期的62行,而且可以通过统计最后的child为-1的值,得到产生的子进程的总数目。因为第一次产生子进程时候,子进程运行到此会因为没有子进程而返回-1错误,可以用perror()查看错误原因为”No child processes"。

 

第二题:问下面的代码执行后总共产生了多少进程(不包括主进程)?(2009 EMC笔试)

 

#includeint main(){        fork();        fork() && fork() || fork();        fork();}

题目的解法在原文中可以得到,这里列出基本解决方法,验证才是本文重点。

题目考察两方面的知识:1、fork()返回值;2、&&和||的运算。

&&是“逻辑与”操作,如果两个操作数有一个为0,则整个式子为0。标准C中规定,如果&&运算符的左操作数为0,则不计算右操作数;如果左操作数为1,才计算右操作数。与之类似,||操作符是“逻辑或”操作,标准C规定如果||运算符左操作数为1,则不计算右操作数;如果左操作数为0,则计算右操作数。

继续来看我们的题目,我们把题目中的5个fork()分别标记为A,B,C,D,E。则我们可以看到,主进程一共产生4个进程,分别产生在A,B,C,E位置上(B,C两个fork()返回值都不是0,因此B&&C不为0,因此不计算D)。

因为同样的原因看,用程序无法直接得到进程数目。而且此处不像上一个题目一样能够在fork()之后增加wait()操作。我们仍然采用增加wait()操作的方法,方法为对fork()函数进行包裹,函数的形参与返回值与原函数相同,不过在中间增加wait()。如此可以保证子进程顺利结束。而且在程序的最后增加一个输出操作,输出的操作符数目即是进程总数目,但是记住输出一定要使用换行符"\n"或者清空缓冲区,否则最后的输出符号数目并不是进程的总数目,此处将在第三题说明。代码如下:

 

int main(int argc, char** argv){   int i;   int status;   pid_t pid;   fflush(stdout);   Fork();   Fork() && Fork() || Fork();   Fork();   printf("+\n");   return 0;}pid_t Fork(){   int status;   pid_t pid;   pid = fork();   wait(&status);   return pid;}

最终的输出结果为20,除去主进程,产生了19个子进程。

 

第三题:请问下面的程序输出多少个“-”符号。原文讲的较好,本文就不在重新解释,只是认为这是三个题目联系较大,可以一起学习。

题目来源:

#include <stdio.h>#include <sys/types.h>#include <unistd.h> intmain(void){   inti;   for(i=0; i<2; i++){      fork();      printf("-");   }    return0;}

分析fork()的机制比较熟悉的话,这个题并不难,输出应该是6个“-”,但是,实际上这个程序会很tricky地输出8个“-”。

要讲清这个题,我们首先需要知道fork()系统调用的特性

(1) fork()系统调用是Unix下以自身进程创建子进程的系统调用,一次调用,两次返回,如果返回是0,则是子进程,如果返回值>0,则是父进程(返回值是子进程的pid),这是众为周知的。

(2) 还有一个很重要的东西是,在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,包括指令,变量值,程序调用栈,环境变量,缓冲区,等等。

所以,上面的那个程序为什么会输入8个“-”,这是因为printf(“-”);语句有buffer,所以,对于上述程序,printf(“-”);把“-”放到了缓存中,并没有真正的输出,在fork的时候,缓存被复制到了子进程空间,所以,就多了两个,就成了8个,而不是6个。

另外,多说一下,我们知道,Unix下的设备有“块设备”和“字符设备”的概念,所谓块设备,就是以一块一块的数据存取的设备,字符设备是一次存取一个字符的设备。磁盘、内存都是块设备,字符设备如键盘和串口。块设备一般都有缓存,而字符设备一般都没有缓存。

对于上面的问题,我们如果修改一下上面的printf的那条语句为:

 

printf("-");fflush(stdout);
或者

printf("-\n");

就没有问题了(就是6个“-”了),因为程序遇到“\n”,或是EOF,或是缓中区满,或是文件描述符关闭,或是主动flush,或是程序退出,就会把数据刷出缓冲区。需要注意的是,标准输出是行缓冲,所以遇到“\n”的时候会刷出缓冲区,但对于磁盘这个块设备来说,“\n”并不会引起缓冲区刷出的动作,那是全缓冲,你可以使用setvbuf来设置缓冲区大小,或是用fflush刷缓存。

原创粉丝点击