最近在深入理解计算机系统(CSAPP)这门课上学到了异常控制流(Exception Control Flow)这一章节,其中书上关于fork()有许多理解。
我们今天就先来做一道面试题目理解理解fork()函数具体的含义。
1.第一题题目描述
请问上图会输出几次Hello?
根据下面的手绘图,我们可以看到总共有6个printf,于是我们不难猜到答案是6个Hello。
所以直接把代码在Ubuntu运行一下试试。
我们可以看到的确是6个Hello,而到这里的话,第一题也已经讲完啦。
2.第二题题目描述
请问上图会输出几次Hello?
看到这里,可能会有小伙伴有疑问了,会翻到上面去看这题和上题难道有什么不同吗?
细心的小伙伴发现这道题目把 Hello 后面的 \n 给去掉了。
\n 在c语言里面不就是换行的意思吗?
所以答案应该还是6个Hello。
我们依旧在Ubuntu里面实验一下。
重新编译后再次运行。
我们可以发现系统输出了8个Hello。
原因是
在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,包括指令,变量值,环境变量,缓冲区,等等。
而printf语句其实并不会马上把结果输出到屏幕上,而是先到缓存区里面,在进行缓存区输出到屏幕。
\n 其实不仅仅代表着换行的意思,还包括清空缓存区。我们在调用fork()时,缓存区也被复制到子进程。
所以在最后,由原本的父进程输出2次,子进程输出2次。变成了父进程输出4次,子进程输出8次。
最后我们还可以验证一下我们的结论,一般fflush(stdout)是用来刷新缓存,我们把它加进程序,重新编译看看效果。
我们可以看到最后只输出了6个Hello。
3.第三题题目描述
我们可以看到运行出来的结果是 Linux Hello
那么,有什么办法改一下if中的条件让输出顺序换一下,换成Hello Linux吗?
做这题我们要知道fork函数有两个返回值,fork()的作用是产生一个子进程。
如果调用成功则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1。
上图就是因为父进程快于子进程运行,所以先输出Linux,再输出Hello。
所以我们的改动是加上一个wait()函数。
wait函数的作用是会暂停当前进程,直到子进程结束。
进程一旦调用了wait,就会马上阻塞自己,由wait()自动分析当前进程的某个子进程是否已经退出,如果它找到了这样一个已经变成僵尸进程的子进程,wait()收集此子进程信息后销毁且返回。
所以,在fork()之后加上wait()进行判断,此时主进程由fork()返回子进程ID(非0)并被wait()强制暂停;子进程由fork()创造并返回0,打印Hello随后被销毁。
我们就可以直接看代码和结果了。系统输出的是HelloLinux。
4.第四题题目描述
int main(int argc, char* argv[])
{
fork();
fork() && fork() || fork();
fork();
}
请问总共创建了多少个进程?
这个题目要解出来主要看第4行代码。
第3行和第5行都是只有一个fork(),所以会产生4个分支,剩下的就是看如何解析第4行了。
当 A && B,如果A = 0,则不继续执行 && B。如果A≠0,则继续执行 && B。
当 A || B,如果 A ≠ 0,则不继续执行 , 如果A = 0,则继续执行 || B。
假设当我们仅执行第4行代码时,手绘图如下:
我们可以看到总共有5个分支。
前面算出2个fork()是4个分支,所以4×5 = 20,总共有20个分支,去掉main主线程。
我们就可以得到答案是创建了19个线程。