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

本系列文章节选自本人所著《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行。




  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值