您的位置:首页 > 其它

进程控制天字第1号系统调用——fork

2013-09-13 20:21 232 查看



本系列文章节选自本人所著《Linux下C语言应用编程》



本系列文章,所需代码请从以下地址下载:

http://download.csdn.net/download/scyangzhu/5129027

1.1.1 fork的机制与特性

1 #include <stdio.h>

2#include <unistd.h>

3#include <stdlib.h>

4

5int main(void)

6 {

7 pid_t pid;

8 if ((pid = fork()) == 0){

9 getchar();

10 exit(0);

11 }

12 getchar();

13 }

14

父进程成功调用fork(8行)后将会产生一个子进程。此时会有2个问题:

1. 子进程的代码从哪里来?

2. 子进程首次被OS调度时,执行的第1条代码是哪条代码?

子进程的代码是父进程代码的一个完全相同拷贝。事实上不仅仅是text段,子进程的全部进程空间(包括:text/data/bss/heap/stack/command line/environment variables)都是父进程空间的一个完全拷贝。

下一个问题是:谁为子进程分配了内存空间?谁拷贝了父进程空间的内容到子进程的内存空间?fork当仁不让!事实上,查看fork实现的源代码,由4部分工作组成:首先,为子进程分配内存空间;然后,将父进程空间的全部内容拷贝到分配给子进程的内存空间;然后在内核数据结构中创建并正确初始化子进程的PCB(包括2个重要信息:子进程pid,PC的值=善后代码的第1条指令地址);最后是一段善后代码。

由于子进程的PCB已经产生,所以子进程已经出生,因此子进程就可以被OS调度到来运行。子进程首次被OS调度时,执行的第1条代码在fork内部,不过从应用程序的角度来看,子进程首次被OS调度时,执行的第1条代码是从fork返回。这就导致了fork被调用1次,却返回2次:父、子进程中各返回1次。对于应用程序员而言,最重要的是fork的2次返回值不一样,父进程返回值是子进程的pid,子进程的返回值是0。

至于子进程产生后,父、子进程谁先运行,取决于OS调度策略,应用程序员无法控制。

以上分析了fork的内部实现以及对应用程序的影响。如果应用程序员觉得难以理解的话,可以暂时抛开,只要记住3个结论即可:

1. fork函数被调用一次(在父进程中被调用),但返回两次(父、子进程中各返回一次)。两次返回的区别是子进程的返回值是0,而父进程的返回值则是子进程的进程ID。

2. 父、子进程完全一样(代码、数据),子进程从fork内部开始执行;父、子进程从fork返回后,接着执行下一条语句。

3. 一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,应用程序员无法控制。

1.1.2 fork使用实例分析

下面来分析一个使用fork的程序。

forkbase.c

1 #include <stdio.h>

2 #include <stdlib.h>

3 #include <unistd.h>

4 #include <sys/types.h>

5

6 int glob = 6; /* external variable ininitialized data */

7 char buf[ ] = "a writeto stdout\n";

9 int main(void)

10 {

11 int var; /* automatic variable on the stack*/

12 pid_t pid;

13 var = 88;

14 if ((write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1))

15 { printf("writeerror\n"); exit(1); }

16 printf("before fork\n"); /* we don't flush stdout */

18 if ( (pid = fork()) < 0)

19 { printf("forkerror\n"); exit(1); }

20 else if (pid == 0) { /*child */

21 glob++; /* modifyvariables */

22 var++;

23 } else

24 sleep(2); /* parent */

25 printf("pid = %d, ppid = %d, glob = %d, var = %d\n",getpid(),getppid(), glob, var);

26 exit(0);

27 }

运行结果:

1 a write to stdout

2 before fork

3 pid = 2845, ppid = 2844,glob = 7, var = 89

4 pid = 2844, ppid = 2513, glob = 6, var = 88

运行结果分析:

结果的第1行是由父进程的14行打印。

结果的第2行是由父进程的16行打印。

由于父进程在24行睡眠了2秒,因此fork返回后,子进程先于父进程运行是大概率事件,所以子进程运行到25行打印出结果中的第3行。由于子进程会拷贝父进程的整个进程空间(这其中包括数据),因此当子进程18行从fork返回后,子进程中的glob=6,var=88(拷贝自父进程的数据)。此时子进程中pid=0,因此子进程会执行21、22行,当子进程到达25行时,将打印glob=7,var=89。

虽然,子进程改变了glob和var的值,但它仅仅是改变了子进程中的glob和var,而影响不了父进程中的glob和var。在子进程出生后,父、子进程的进程空间(代码、数据等)就是独立,互不干扰的。因此当父进程运行到25行,将会打印父进程中的glob和var的值,他们分别是6和88,这就是运行结果的第4行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: