对fork函数的理解

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

对fork函数的理解

前言:对于刚刚接触Unix/Linux操作系统,在Linux下编写多进程的人来说,fork 是最难理解的概念之一:它执行一次却返回两个值。因此,本文着重从以下几个方面来使初学者加深对fork函数的理解和应用:fork函数的机制与特性、fork 函数的两次返回和父子进程的执行顺序介绍、

关键字:fork函数、返回值、父进程、子进程

正文:

一、fork函数的机制与特性

1 #include

2 #include

3 #include

4

5 int main(void)

6 {

7 pid_t pid;

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

9 getchar();

10 exit(0);

11 }

12 getchar ();

13}

14

父进程成功的调用fork(8行)后将会产生一个子进程。此时会有两个问题:1、子进程的代码从哪里来?2、子进程首次被OS调用时,执行的第一条代码是哪条代码?

子进程的代码是父进程代码的一个完全相同拷贝。事实上不仅仅是text 段,子进程中的全部进程空间都是(包括:text/data/bss/heap/commandline/envir onment)父进程空间的一个完全拷贝。下一个问题是谁为子进程分配了内存空间?谁复制了父进程空间的内容到子空间?fork当仁不让。事实上,fork 实现的源代码,由四部分组成:首先,为子进程分配内存空间;然后,将父进程空间的全部内容复制到分配给子进程的内存空间;接着在内核数据结构中创建并正确初始化子进程的PCB (包括两个重要信息:子进程pid,PC 的值=善后代码的第一条指令地址);最后是一段善后代码。

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

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

以上分析了fork的内部实现以及对应用程序的影响,可以得出以下三个结论:

1、fork函数调用一次,但返回两次。但是两次返回值不一样,子进程返回值是0,而父进程的返回值是子进程的ID

2、父、子进程完全一样,包括代码和数据,子进程从fork内部开始,而父进程从fork返回后,接着执行下一条语句

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

二、fork函数的两次返回和父子进程的执行顺序介绍

我们可以了解,调用fork后会返回两个值或者一个值。两个值是指在调用成功的情况下,返回0表示子进程在运行,大于0的数表示父进程在运行,错误情况下就返回一个值,一个小于0的值。在创建成功的情况下,子进程

执行返回0,是因为一个子进程只有一个父进程,所以无需知道它父进程的id,通过getppid()也就可以获取它的值,而父进程运行时,它需要知道它的至此执行对应的子进程是哪个,因为一个父进程可能会有不止一个的子进程,而且在父进程中也没有可以直接获得其子进程pid的库函数。

然后我们需要了解fork的两次返回,一个函数的调用怎么会返回两个值呢,这里要强调的是,它不是返回了两个值,而是返回了两次,一次返回一个值,所以它还是符合函数返回值的特性---只能返回一个值。

fork()是一个经过封装的用户态函数,当用户程序调用了fork函数之后,执行系统调用sys_fork(),而在sys_fork()中直接调用了do_fork()函数,在do_fork()函数中有6个参数。也就是说真正的创建进程实在do_fork函数中实现的,其实向vfork,pthread_creat也都是最终调用的do_fork函数,do_fork 函数对调用它的函数的区别是通过clone_flags标志来实现的。

long do_fork(unsignedlong clone_flags, unsigned long stack_start,

struct pt_regs *regs,

unsigned long stack_size,

int __user *parent_tidptr,

int __user *child_tidptr)

在do_fork函数中又调用了copy_process函数,在这个函数里面,先用位图法的方式给新进程分配一个pid,然后再为新进程分配PCB资源,先将新进程要复制父进程的资源复制到它的PCB中,然后再为其PCB中的其他变量赋值。一般当一个进程的PCB 创建好了,这个进程也就存在了,那么此时已经存在两个进程了,一个是父进程,一个是新创建的子进程,子进程若执行则返回0,父进程执行则返回子进程的pid。所以两次返回,指的是子进程和父进程各返回了一个值。对于父子进程执行顺序的问题:也是在do_fork函数中,它会有一个标志性的变量,根据其不同取值,来决定先让谁执行,比如子进程先执行然后再把父进程插入到队列中,简单来说就是在内核的实现过程中,父子进程的执行顺序还是由程序本身来决定的,但是到了用户态看起来就是随机的了。

结语:

(1)fork()系统调用是创建一个新进程的首选方式,fork的返回值要么是0,要么是非0,父进程与子进程的根本区别在于fork函数的返回值.

(2)vfork()系统调用除了能保证用户空间内存不会被复制之外,它与fork几乎是完全相同的.vfork存在的问题是它要求子进程立即调用exec,而不用修改任何内存,这在真正实现的时候要困难的多,尤其是考虑到exec调用有可能失败.

(3)vfork()的出现是为了解决当初fork()浪费用户空间内存的问题,因为在fork()后,很有可能去执行exec(),vfork()的思想就是取消这种复制.

(4)现在的所有unix变量都使用一种写拷贝的技术(copy on write),它使得一个普通的fork调用非常类似于vfork.因此vfork变得没有必要.

相关文档
最新文档