赞
踩
距上一次写博客已经过去很久了,(这次也是要求的),不过这次的学习过程是真的就如标题一样,一开始万分绝望,当时的心境仿佛孑身一人于孤岛之上,放眼望去只有看不到边的海平面;而之后却也还是得到了新生,就仿佛眼见从那海平面上缓缓驶来一艘游船......
这一次的学习的主要内容是实操fork函数相关的代码,并对其进行分析。老实说对这个函数其实并不陌生,因为大一的操作系统课就曾学过了。但这么久以来,它就好像一个熟悉的陌生人一样(明明知道她的名字,却不知如何上前搭话),从那学期学完考完后就从来没有再用过它,所以也就才有了一开始的糗态。但真正动手了之后,才发现,它其实还蛮有趣的(有趣的灵魂)。
fork函数最重要,最有趣,也是最令人头痛的一点大概就是“一次调用,两次返回”了正是这个特性,使得代码仿佛有了生命一般,反复左右横跳,总让人捉摸不定。下面我们就直接用一些例子来说明吧。
- Fork.c
-
- #include <stdio.h>
- #include<unistd.h>
-
-
- pid_t Fork(void)
- {
- pid_t pid;
-
- if((pid = fork()) < 0){
- printf("Fork error");
- exit(0)
- )
- return pid;
- }
这个是之后所有例子都会用到的Fork函数的实现,简单说明一下:就是把原本的fork()函数重新包装了一下,使其具有了当pid号出现明显错误(pid < 0)时,会进行报错并直接退出程序。
那么首先是第一个非常简单的例子:
- fork.c
-
- #include <stdio.h>
- #include<unistd.h>
-
- pid_t Fork();
- int main(int argc, char *argv[])
- {
- pid_t pid;
- int x = 1;
-
- pid = Fork(); //forkreturn
- if (pid == 0) { /* Child */
- printf("child : x=%d\n", ++x); //childprint
- fflush(stdout);
- return 0;
- }
-
- /* Parent */
- printf("parent: x=%d\n", --x); //parentprint
- fflush(stdout);
- return 0;
- }
这个例子的输出结果如下:
- 输出结果
-
-
- parent: x=0
- qioqio@ubuntu:~/homework/chap8_code$ child : x=2
当然这只是在我的电脑上输出的结果,如果自己去试的话,可能会与这个结果有所不同(可能先输出第二行再输出第一行)这是因为利用fork()函数生成的子进程和其父进程本质上是两个相互独立的进程,并不能确定谁会先运行(在我这是长辈腿长走得快)。
然后我们再来分析下这个例子:
这个例子很简单,没有什么嵌套或者循环,就是顺序执行,而且只有一个fork()函数,那么就是程序运行到fork()函数后一分为二,变成一个子进程,一个父进程;子进程打印“child: x = 2”,进程结束;父进程打印“parent: x = 0”,进程结束。进程图如下所示:
然后之后是第二个例子:
- forkx2.c
-
- #include <stdio.h>
- #include<unistd.h>
-
- pid_t Fork();
-
- int main(int argc, char *argv[])
- {
- pid_t pid;
- int x = 1;
-
- pid = Fork(); //forkreturn
- if (pid == 0) { /* Child */
- printf("child : x=%d\n", ++x);
- printf("child : x=%d\n", ++x);
- fflush(stdout);
- return 0;
- }
-
- /* Parent */
- printf("parent: x=%d\n", --x);
- printf("parent: x=%d\n", --x);
- fflush(stdout);
- return 0;
- }
这个例子在第一个例子上稍微进了一步,在每个进程上再增加了一次打印,所以输出结果是这样的:
- 输出结果
-
- parent: x=0
- parent: x=-1
- qioqio@ubuntu:~/homework/chap8_code$ child : x=2
- child : x=3
-
接下来还是来分析一下这个例子,这个例子还是只有一个fork()函数,所以还是一分为二,一个子进程,一个父进程。还是之前说的子进程和父进程本质上是两个独立的进程,所以它们所用的x值也是相互独立的,所以父进程打印的是"x = 0, x = -1",子进程打印的是"x = 2, x = 3"。进程图如下所示:
接下来是下一个例子:
- fork2.c
-
- void fork2()
- {
- printf("L0\n");
- fork();
- printf("L1\n");
- fork();
- printf("Bye\n");
- }
这个例子里没有用到Fork()(因为直接从老师那借来的),它较上两个例子就增加了一个fork()函数,所以难度也增加了一些,输出结果如下:
- 输出结果:
-
- L0
- L1
- Bye
- qioqio@ubuntu:~/homework/chap8_code$ Bye
- L1
- Bye
- Bye
接下来照例是对这个例子的分析,这个例子里有两个fork() 函数,所以在第一次遇到fork() 函数一分为二后,在子进程和父进程中都还分别有一个fork() 函数,也就是还要再一次一分为二。在第一次遇到fork() 函数前,程序就有一个打印"L0"的函数,所以"L0"一定是第一个打印的。之后又到了子进程和父进程的时间,所以打印的结果也就会有很多种,在我这只是其中的一种,遇到第一个fork()后有一个打印"L1"的函数,所以接着会打印"L1",然后再遇到第二个fork() 函数,再分别打印一次"Bye"。但切记不可能先打印"Bye"再打印"L1",因为进程图都是线性的不能回转!然后这里没有标注父子进程的语句,所以我们也不得而知是哪个进程先进行。进程图如下所示:
然后是一个关于嵌套的例子:
- fork4.c
-
- void fork4()
- {
- printf("L0\n");
- if (fork() != 0) {
- printf("L1\n");
- if (fork() != 0) {
- printf("L2\n");
- }
- }
- printf("Bye\n");
- }
同样是从老师那借来的,所以也没用Fork()函数,在这个例子里有嵌套选择的存在,输出结果如下:
- 输出结果
-
- L0
- L1
- L2
- Bye
- qioqio@ubuntu:~/homework/chap8_code$ Bye
- Bye
照例的分析,因为存在嵌套选择,所以情况又有所不同,首先是在遇到fork() 函数之前打印的"L0",,然后就到了嵌套选择,第一次进行选择的条件是"fork() != 0", 然后我们知道对于fork() 函数来说子进程会返回0,而父进程会返回子进程的PID号,所以这个条件翻译过来就是“当此时不是子进程时”,就会执行"L1"的打印。然后再是同样的选择条件“fork() != 0”,则会打印"L2",之后就跳出选择,再打印"Bye"。总结一下就是只有父进程会打印"L1",然后父进程的父进程会打印"L2"其他就只会打印"Bye"。(其次注意这里只有第一次fork()产生的父进程才能进行第二次fork)进程图如下:
接下来我们连着看两个例子:
首先是第一个:
- forkprob1.c
-
- #include <stdio.h>
- #include<unistd.h>
- #include<stdlib.h>
-
- pid_t Fork();
-
- void doit()
- {
- if(Fork() == 0){
- Fork();
- printf("hello\n");
- exit(0);
- }
- return;
- }
-
- int main()
- {
- doit();
- printf("hello\n");
- exit(0);
- }
然后是第二个:
- forkprob2.c
-
- #include <stdio.h>
- #include<unistd.h>
- #include<stdlib.h>
-
- pid_t Fork();
-
- void doit()
- {
- if(Fork() == 0){
- Fork();
- printf("hello\n");
- return;
- }
- return;
- }
-
- int main()
- {
- doit();
- printf("hello\n");
- exit(0);
- }
怎么样,这两个例子乍眼看上去是不是好像完全一样(但如果一样我也不会放到这了),如果你再仔细看看的话,就会发现在两个例子里的doit() 函数里有一条语句是不同的,第一个例子if条件下是“exit(0);”,而第二个例子下的则是“return;”也许你会想这两个不都是退出吗,有什么区别?但事实是第二个例子比第一个例子整整多打印了两个“hello”!!!输出结果如下:
- forkprob1输出结果
-
- hello
- qioqio@ubuntu:~/homework/chap8_code$ hello
- hello
- forkprob6输出结果
-
- hello
- qioqio@ubuntu:~/homework/chap8_code$ hello
- hello
- hello
- hello
但这是为什么呢?明明都是退出,得到的结果却如此之大。原因当然就是在这两个“退出”上,exit()是系统调用级别的,它表示了一个进程的结束;return是语言级别的,它表示了调用堆栈的返回。用更通俗的话来讲就是,exit()是进程的结束,而return只是函数的结束(当然如果把return放在主函数main里它的效果就和exit()一样了,因为此时是退出主函数,也就是整个程序)。那么放在我们这两个例子里是什么情况呢?因为doit() 函数里的if条件调用了Fork() 函数,所以两个例子都会进行一次fork,然后因为子进程满足if条件,那么子进程会执行if下的语句,再进行一次fork。然后第一个例子里子进程打印完“hello”后就调用exit(),这样子进程就不复存在了,也就不能再执行主函数里打印“hello”的语句;而在第二个例子里子进程打印完后仅仅是调用return语句返回退出当前的doit()函数,其本身依旧存在,所以还是能执行主函数里打印“hello”的语句,所以就会多出两个“hello”了。以下为进程图:
例子1:
例子2:
至此,本次的学习笔记就告一段落了,总之能把fork弄明白真是太好了!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。