收到autoCAD告知函:fork 函数详解

来源:百度文库 编辑:中财网 时间:2024/04/28 14:50:39
#include #include pid_t fork(void);

fork调用失败则返回-1,调用成功的返回值见下面的解释。我们通过一个例子来理解fork是怎样创建新进程的。

例 30.3. fork

#include #include #include #include int main(void){pid_t pid;char *message;int n;pid = fork();if (pid < 0) {perror("fork failed");exit(1);}if (pid == 0) {message = "This is the child\n";n = 6;} else {message = "This is the parent\n";n = 3;}for(; n > 0; n--) {printf(message);sleep(1);}return 0;}

$ ./a.out This is the childThis is the parentThis is the childThis is the parentThis is the childThis is the parentThis is the child$ This is the childThis is the child

这个程序的运行过程如下图所示。

图 30.4. fork


  1. 父进程初始化。

  2. 父进程调用fork,这是一个系统调用,因此进入内核。

  3. 内核根据父进程复制出一个子进程,父进程和子进程的PCB信息相同,用户态代码和数据也相同。因此,子进程现在的状态看起来和父进程一样,做完了初始化,刚调用了fork进入内核,还没有从内核返回

  4. 现在有两个一模一样的进程看起来都调用了fork进入内核等待从内核返回(实际上fork只调用了一次),此外系统中还有很多别的进程也等待从内核返回。是父进程先返回还是子进程先返回,还是这两个进程都等待,先去调度执行别的进程,这都不一定,取决于内核的调度算法。

  5. 如果某个时刻父进程被调度执行了,从内核返回后就从fork函数返回,保存在变量pid中的返回值是子进程的id,是一个大于0的整数,因此执行下面的else分支,然后执行for循环,打印"This is the parent\n"三次之后终止。

  6. 如果某个时刻子进程被调度执行了,从内核返回后就从fork函数返回,保存在变量pid中的返回值是0,因此执行下面的if (pid == 0)分支,然后执行for循环,打印"This is the child\n"六次之后终止。fork调用把父进程的数据复制一份给子进程,但此后二者互不影响,在这个例子中,fork调用之后父进程和子进程的变量messagen被赋予不同的值,互不影响。

  7. 父进程每打印一条消息就睡眠1秒,这时内核调度别的进程执行,在1秒这么长的间隙里(对于计算机来说1秒很长了)子进程很有可能被调度到。同样地,子进程每打印一条消息就睡眠1秒,在这1秒期间父进程也很有可能被调度到。所以程序运行的结果基本上是父子进程交替打印,但这也不是一定的,取决于系统中其它进程的运行情况和内核的调度算法,如果系统中其它进程非常繁忙则有可能观察到不同的结果。另外,读者也可以把sleep(1);去掉看程序的运行结果如何。

  8. 这个程序是在Shell下运行的,因此Shell进程是父进程的父进程。父进程运行时Shell进程处于等待状态(第 3.3 节 “wait和waitpid函数”会讲到这种等待是怎么实现的),当父进程终止时Shell进程认为命令执行结束了,于是打印Shell提示符,而事实上子进程这时还没结束,所以子进程的消息打印到了Shell提示符后面。最后光标停在This is the child的下一行,这时用户仍然可以敲命令,即使命令不是紧跟在提示符后面,Shell也能正确读取。

fork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。从上图可以看出,一开始是一个控制流程,调用fork之后发生了分叉,变成两个控制流程,这也就是“fork”(分叉)这个名字的由来了。子进程中fork的返回值是0,而父进程中fork的返回值则是子进程的id(从根本上说fork是从内核返回的,内核自有办法让父进程和子进程返回不同的值),这样当fork函数返回后,程序员可以根据返回值的不同让父进程和子进程执行不同的代码。

fork的返回值这样规定是有道理的。fork在子进程中返回0,子进程仍可以调用getpid函数得到自己的进程id,也可以调用getppid函数得到父进程的id。在父进程中用getpid可以得到自己的进程id,然而要想得到子进程的id,只有将fork的返回值记录下来,别无它法。

fork的另一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程中相同编号的文件描述符在内核中指向同一个file结构体,也就是说,file结构体的引用计数要增加。

gdb调试多进程的程序会遇到困难,gdb只能跟踪一个进程(默认是跟踪父进程),而不能同时跟踪多个进程,但可以设置gdbfork之后跟踪父进程还是子进程。以上面的程序为例:

$ gcc main.c -g$ gdb a.outGNU gdb 6.8-debianCopyright (C) 2008 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "i486-linux-gnu"...(gdb) l2#include 3#include 4#include 56int main(void)7{8pid_t pid;9char *message;10int n;11pid = fork();(gdb) 12if(pid<0) {13perror("fork failed");14exit(1);15}16if(pid==0) {17message = "This is the child\n";18n = 6;19} else {20message = "This is the parent\n";21n = 3;(gdb) b 17Breakpoint 1 at 0x8048481: file main.c, line 17.(gdb) set follow-fork-mode child(gdb) rStarting program: /home/akaedu/a.out This is the parent[Switching to process 30725]Breakpoint 1, main () at main.c:1717message = "This is the child\n";(gdb) This is the parentThis is the parent

set follow-fork-mode child命令设置gdbfork之后跟踪子进程(set follow-fork-mode parent则是跟踪父进程),然后用run命令,看到的现象是父进程一直在运行,在(gdb)提示符下打印消息,而子进程被先前设的断点打断了。