`
coolerbaosi
  • 浏览: 727142 次
文章分类
社区版块
存档分类
最新评论

fork返回值

 
阅读更多

本文来自CSDN博客http://blog.csdn.net/guichen83/archive/2009/05/08/4160697.aspx

fork简介:
fork英文原意是“分岔,分支”的意思,而在操作系统中,乃是著名的Unix(或类Unix,如Linux,Minix)中用于创建子进程的系统调用。

【NOTE1】
fork () 的作用是什么?换句话说,你用 fork () 的目的是什么?
――是为了产生一个新的进程,地球人都知道 :)
产生一个什么样的进程?
――和你本来调用 fork () 的那个进程基本一样的进程,其实就是你原来进程的副本;
真的完全一样吗?
――当然不能完全一样,你要两个除了 pid 之外其它一模一样的进程干什么,就算memory
再多也不用这么摆谱吧?
哪里不一样?
――当然最重要的是 fork () 之后执行的代码不一样,you know, i know :)
怎么实现呢?
――如果是 Windows,它会让你在 fork () 里面提供一大堆东西,指明这个那个什么的……
我用的是 unix 啊
――所以很简单,unix 会让两个进程(不错,原来是一个,unix 替你复制了一个,现在有两个)
在 fork () 之后产生不同:返回值不同。其中一个进程(使用新的 pid)里面的 fork () 返回零,
这个进程就是“子进程”;而另一个进程(使用原来的 pid)中的 fork () 返回前面那个子进程的
pid,他自己被称为“父进程”
然后呢?
――写代码的人又不笨,当然就根据返回值是否非零来判断了,现在我是在子进程里面呢,还是在
父进程里面?在子进程里面就执行子进程该执行的代码,在父进程里面就执行父进程的代码……

有铁杆 windows fans 借此说明,windows 好啊,子进程用子进程的代码,父进程用父进程的,
你 unix 笨了吧,子进程包含父进程、子进程的代码,父进程包含父进程子进程的代码,岂不是多占用内存了吗?
――据我所知,unix 代码段都是可重入代码,也就是说,进程复制,并不复制代码段,若干个进程
共享同一代码段,增加的只是全局共享数据和对文件描述符的引用等,另外就是堆栈。你一个代码
长达 10M 的进程,fork () 出三四个子进程,只是增加一点内存占用(如果你没有使用很多全局变量
的话),而不是占用 40M 以上的内存。

【NOTE2】
程序 从 fork 开始分支 (称分支不准确), 一路是主进程 pid > 0 (pid 是子进程ID) 一路是子进程 pid == 0 自此分成两个任务
其实fork的时候已经两个分支了,数据段被复制了一份,因此pid有两份
执行pid=fork()时,返回值赋给pid在两个进程中运行,
fork会返回给父进程的那个>0的值,告诉调用者新建进程的pid
子进程的fork返回值是0
更不用说if...else的比较也是在两个进程中都做的了

【NOTE3】
fork的精辟剖析
程序如下:
#include <unistd.h>;
#include <sys/types.h>;
main ()

{ pid_t pid;
pid=fork();

if (pid < 0) printf("error in fork!");
else if (pid == 0)
printf("i am the child process, my process id is %dn",getpid());
else
printf("i am the parent process, my process id is %dn",getpid());
}

结果是

[root@localhost c]# ./a.out

i am the child process, my process id is 4286

i am the parent process, my process id is 4285

一:
要搞清楚fork的执行过程,就必须先讲清楚操作系统中的“进程(process)”概念。一个进程,主要包含三个元素:

o. 一个可以执行的程序;

o. 和该进程相关联的全部数据(包括变量,内存空间,缓冲区等等);

o. 程序的执行上下文(execution context)。

不妨简单理解为,一个进程表示的,就是一个可执行程序的一次执行过程中的一个状态。操作系统对进程的管理,典型的情况,是通过进程表完成的。进程表中的每 一个表项,记录的是当前操作系统中一个进程的情况。对于单 CPU的情况而言,每一特定时刻只有一个进程占用 CPU,但是系统中可能同时存在多个活动的(等待执行或继续执行的)进程。一个称为“程序计数器(program counter, pc)”的寄存器,指出当前占用 CPU的进程要执行的下一条指令的位置。当分给某个进程的 CPU时间已经用完,操作系统将该进程相关的寄存器的值,保存到该进程在进程表中对应的表项里面;把将要接替这个进程占用 CPU的那个进程的上下文,从进程表中读出,并更新相应的寄存器(这个过程称为“上下文交换(process context switch)”,实际的上下文交换需要涉及到更多的数据,那和fork无关,不再多说,主要要记住程序寄存器 pc指出程序当前已经执行到哪里,是进程上下文的重要内容,换出 CPU的进程要保存这个寄存器的值,换入CPU的进程,也要根据进程表中保存的本进程执行上下文信息,更新这个寄存器)。

好了,有这些概念打底,可以说fork了。当你的程序执行到下面的语句:

pid=fork();

操作系统创建一个新的进程(子进程),并且在进程表中相应为它建立一个新的表项。新进程和原有进程的可执行程序是同一个程序;上下文和数据,绝大部分就是 原进程(父进程)的拷贝,但它们是两个相互独立的进程!此时程序寄存器pc,在父、子进程的上下文中都声称,这个进程目前执行到fork调用即将返回(此 时子进程不占有CPU,子进程的pc不是真正保存在寄存器中,而是作为进程上下文保存在进程表中的对应表项内)。问题是怎么返回,在父子进程中就分道扬 镳。

父进程继续执行,操作系统对fork的实现,使这个调用在父进程中返回刚刚创建的子进程的pid(一个正整数),所以下面的if语句中pid<0, pid==0的两个分支都不会执行。所以输出i am the parent process...

子进程在之后的某个时候得到调度,它的上下文被换入,占据 CPU,操作系统对fork的实现,使得子进程中fork调用返回0。所以在这个进程(注意这不是父进程了哦,虽然是同一个程序,但是这是同一个程序的另 外一次执行,在操作系统中这次执行是由另外一个进程表示的,从执行的角度说和父进程相互独立)中pid=0。这个进程继续执行的过程中,if语句中 pid<0不满足,但是pid= =0是true。所以输出i am the child process...

为什么看上去程序中互斥的两个分支都被执行了?在一个程序的一次执行中,这当然是不可能的;但是你看到的两行输出是来自两个进程,这两个进程来自同一个程序的两次执行。

fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据 空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,但只有一点不同,如果fork成功,子进程中fork的返回值是0, 父进程中fork的返回值是子进程的进程号,如果fork不成功,父进程会返回错误。

可以这样想象,2个进程一直同时运行,而且步调一致,在fork之后,他们分别作不同的工作,也就是分岔了。这也是fork为什么叫fork的原因。

在程序段里用了fork()之后程序出了分岔,派生出了两个进程。具体哪个先运行就看该系统的调度算法了。

如果需要父子进程协同,可以通过原语的办法解决。

二:
进程的创建:
创建一个进程的系统调用很简单.我们只要调用fork函数就可以了.
#include <unistd.h>
pid_t fork();

当一个进程调用了fork以后,系统会创建一个子进程.这个子进程和父进程不同的地方只有他的进程ID和父进程ID,其他的都是一样.就象父进程克隆 (clone)自己一样.当然创建两个一模一样的进程是没有意义的.为了区分父进程和子进程,我们必须跟踪fork的返回值. 当fork掉用失败的时候(内存不足或者是用户的最大进程数已到)fork返回-1,否则fork的返回值有重要的作用.对于父进程fork返回子进程的 ID,而对于fork子进程返回0.我们就是根据这个返回值来区分父子进程的. 父进程为什么要创建子进程呢?前面我们已经说过了Linux是一个多用户操作系统,在同一时间会有许多的用户在争夺系统的资源.有时进程为了早一点完成任 务就创建子进程来争夺资源. 一旦子进程被创建,父子进程一起从fork处继续执行,相互竞争系统的资源.有时候我们希望子进程继续执行,而父进程阻塞,直到子进程完成任务.这个时候 我们可以调用wait或者waitpid系统调用.

总结一下有三:

1,派生子进程的进程,即父进程,其pid不变;

2,对子进程来说,fork返回给它0,但它的pid绝对不会是0;之所以fork返回0给它,是因为它随时可以调用getpid()来获取自己的pid;

3,fork之后父子进程除非采用了同步手段,否则不能确定谁先运行,也不能确定谁先结束。认为子进程结束后父进程才从fork返回的,这是不对的,fork不是这样的,vfork才这样。


【NOTE4】

首先必须有一点要清楚,函数的返回值是储存在寄存器eax中的。
其次,当fork返回时,新进程会返回0是因为在初始化任务结构时,将eax设置为0;
在fork中,把子进程加入到可运行的队列中,由进程调度程序在适当的时机调度运行。也就是从此时开始,当前进程分裂为两个并发的进程。
无论哪个进程被调度运行,都将继续执行fork函数的剩余代码,执行结束后返回各自的值。

【NOTE5】
对于fork来说,父子进程共享同一段代码空间,所以给人的感觉好像是有两次返回,其实对于调用fork的父进程来说,如果fork出来的子进程没有得到 调度,那么父进程从fork系统调用返回,同时分析sys_fork知道,fork返回的是子进程的id。再看fork出来的子进程,由 copy_process函数可以看出,子进程的返回地址为ret_from_fork(和父进程在同一个代码点上返回),返回值直接置为0。所以当子进 程得到调度的时候,也从fork返回,返回值为0。
关键注意两点:1.fork返回后,父进程或子进程的执行位置。(首先会将当前进程eax的值做为返回值)2.两次返回的pid存放的位置。(eax中)

进程调用copy_process得到lastpid的值(放入eax中,fork正常返回后,父进程中返回的就是lastpid)
子进程任务状态段tss的eax被设置成0,
fork.c 中
p-&gt;tss.eax=0;(如果子进程要执行就需要进程切换,当发生切换时,子进程tss中的eax值就调入eax寄存器,子进程执行时首先会将eax的内容做为返回值)
当子进程开始执行时,copy_process返回eax的值。
fork()后,就是两个任务同时进行,父进程用他的tss,子进程用自己的tss,在切换时,各用各的eax中的值.

所以,“一次调用两次返回”是2个不同的进程!
看这一句:pid=fork()
当执行这一句时,当前进程进入fork()运行,此时,fork()内会用一段嵌入式汇编进行系统调用:int 0x80(具体代码可参见内核版本0.11的unistd.h文件的133行_syscall0函数)。这时进入内核根据此前写入eax的系统调用功能号 便会运行sys_fork系统调用。接着,sys_fork中首先会调用C函数find_empty_process产生一个新的进程,然后会调用C函数 copy_process将父进程的内容复制给子进程,但是子进程tss中的eax值赋值为0(这也是为什么子进程中返回0的原因),当赋值完成后, copy_process会返回新进程(该子进程)的pid,这个值会被保存到eax中。这时子进程就产生了,此时子进程与父进程拥有相同的代码空间,程 序指针寄存器eip指向相同的下一条指令地址,当fork正常返回调用其的父进程后,因为eax中的值是新创建的子进程号,所以,fork()返回子进程 号,执行else(pid&gt;0);当产生进程切换运行子进程时,首先会恢复子进程的运行环境即装入子进程的tss任务状态段,其中的eax 值(copy_process中置为0)也会被装入eax寄存器,所以,当子进程运行时,fork返回的是0执行if(pid==0)。

【NOTE5】
理解它关键在于理解堆栈的切换和压栈,弹栈!
关于子进程的返回:
子进程复制了父进程的栈内容,从高到低
SS
ESP
EFLAGS
CS
EIP -----此是int 0x80 的下一条指令,也是子进程开始执行的地方!!!!
DS
ES
FS
EDX
ECX
EBX
GS
ESI
EDI
EBP
EAX(0)

由于 EAX = 0,所以子进程返回 0 给 fork.

注:新进程的用户栈设为其父进程的用户栈(最后弹出的SS,ESP)。如果父子进程以copy_on_write方式共用用户堆栈
(Linux之下就是这样的),而且在此之前父进程修改了该堆栈(如果父进程先返回,这几乎是肯定的),那么,系统已经为父进程创建了该用户栈的副本,父进程原来的用户栈留给了子进程。那么新进程的系统栈已经清空,新进程回到了用户态,返回到了函数fork。

【NOTE6】
关于fork的讨论与评价:
fork好不好?相比其他操作系统如Windows,Windows会有诸如CreateProcess这样的函数来创建一个与生俱来两手空空的独立的新进程。然后还有一大堆参数,指手画脚的告诉你这个那个是什么。。。

烦!!!

K.I.S.S. (Keep it simple,stupid.)是Unix的至高原则。

fork 起源于 Unix 操作系统。那是贝尔实验室的 K&R (这两人是Unix和C语言之父) 的一项天才发明!!! Linux由于与生俱来就与Unix血浓于水,所以继承了它的这个天才发明。

这种方法效率是很高的。因为复制的代价是很低的。在计算机网络的实现中,以及在 client/server 系统中的server 一方的实现中,fork 常常是最自然,最有效,最适宜的手段。很多人甚至怀疑,到底是先有 fork 还是先有 client/server,因为 fork 似乎就是专门为此而设计的 !更重要的好处是,这样有利于父子进程间通过 pipe 建立起一种简单有效的进程间通信管道,并且产生了操作系统的用户界面即 shell 的管道机制。这一点,对于 Unix 的发展和应用推广,对于 Uinx 程序设计环境的形成,对于 Unix 程序设计风格的形成,都有非常深远的影响。可以说这是一项天才的发明,它在很大程度上改变了操作系统的发展方向。

分享到:
评论

相关推荐

    fork()系统调用参数返回值详解

    linux系统调用fork()的详细剖析,必然令你恍然大悟~~~

    PHP使用pcntl_fork实现多进程下载图片的方法

    PHP pcntl_fork — 在当前进程当前位置产生分支,子进程,译注:fork是创建了一个子进程,父进程和子进程 都从fork的位置开始向下继续执行,不同的是父进程执行过程中,得到的fork返回值为子进程号,而子进程得到的是0. ...

    创建新进程:fork函数:fork函数干什么? fork函数与vfork函数的区别在哪里?为何在一个fork的子进程分支中使用_exit函数而不使用exit函数?

    创建新进程:fork函数:fork函数干什么? fork函数与vfork函数的区别在哪里?为何在一个fork的子进程分支中使用_exit函数而不使用exit函数?

    PHP多进程简单实例小结

    分享给大家供大家参考,具体如下: ...译注:fork是创建了一个子进程,父进程和子进程 都从fork的位置开始向下继续执行,不同的是父进程执行过程中,得到的fork返回值为子进程号,而子进程得到的是0。失败时,在 父

    浅谈Linux vfork与fork简单对比分析

    本文分享了Linux vfork与fork简单对比分析,分享给大家,具体如下: ...返回值:返回值大于0则当前进程为父进程,等于0代表为子进程,小于零代表创建子进程失败。 通过一个例子来了解: #include #inc

    操作系统课程设计试验

    当fork掉用失败的时候(内存不足或者是用户的最大进程数已到)fork返回-1,否则fork的返回值有重要的作用。对于父进程fork返回子进程的ID,而对于fork子进程返回0.我们就是根据这个返回值来区分父子进程的。Linux是一个...

    Linux学习?fork用法

     返回值:子进程中返回0,父进程中返回子进程ID,出错返回-1  在fork调用之后,子进程获得父进程数据空间、堆和栈的副本,但是并不共享这些存储空间部分。  fork的一个特性是父进程的所有打开文件描述符都被...

    浅谈Linux环境下并发编程中C语言fork()函数的使用

    fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getpid以获得其父进程的进程ID。 使fork失败的两个主要原因是:系统中已经有了太多的进程,或者该实际用户ID的进程总数超过了...

    嵌入式系统/ARM技术中的FORK()函数的理解

     返回值:  负数:如果出错,则fork()返回-1,此时没有创建新的进程。最初的进程仍然运行。  零:在子进程中,fork()返回0  正数:在负进程中,fork()返回正的子进程的PID  其次我们来看下如何利用

    C语言的fork函数在Linux中的进程操作及相关面试题讲解

    2.fork 函数被调用一次,但返回两次, 两次返回的唯一区别是子进程的返回值是 0, 而父进程的返回值是新子进程的 PID。 3.子进程和父进程继续执行 fork 调用之后的指令。 在上图的存储空间布局中,父子进程只共享正文...

    php pcntl_fork和pcntl_fork 的用法

    在父进程的代码中可以用pcntl_fork(&$status)暂停父进程知道他的子进程有返回值。注意:父进程的阻塞同时会阻塞子进程。但是父进程的结束不影响子进程的运行。 父进程运行完了会接着运行子进程,这时子进程会从...

    forkjoin:窃取Rust的fork-join并行性库的工作

    在精简样式算法中,每个子任务的返回值都传递到一个特殊的联接函数,该函数在所有子任务都已完成时执行。 如果该算法具有ReduceStyle::Arg则可以直接从任务发送一个额外的自变量给此连接函数。 可以在此处的示例中...

    Fork()函数与Linux中的多线程编程

    一、fork()函数  在操作系统的基本概念中进程是程序的一次执行...fork()函数是Linux系统中一个比较特殊的函数,其一次调用会有两个返回值,下面是fork()函数的声明:  #include  // On success, The PID of the

    Linux系统中C语言编程创建函数fork()执行解析

     (pid_t 是一个宏定义,其实质是int 被定义在#include中) 返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1  (3)函数说明  一个现有进程可以调用fork函数创建一个新...

    python os.fork() 循环输出方法

    首先,你要明白os.fork()创建的子进程会接着下一行代码继续执行,它有返回值,返回值可以为0表示子进程或者大于0表示父进程pid,os.getpid()返回的是当前进程的pid,那么我们再改改输出来看得更直观一

    关于多进程编程中fork()函数返回两次的探究

    而且返回值不同。对于以前的认知大家都知道一个函数只能返回依次啊。  呵呵,这是fork的神奇所在,它为什么这么神奇?它是怎么实现的?下面我根据自己得理解简单的说一下,不过另外也欢迎大家的讨论。  首先要...

    Linux 进程控制与进程通信1

    (1)Fork的返回值有多种,其用法如下Pid=fork() (5)fork出错可能有两种原因:(1)当前的进程数已经达到了系统规定的上限,这时errno的值被

    python创建进程fork用法

    则是子进程,如果返回值&gt;0,则是父进程(返回值是子进程的pid) ''' source = 10 i = 0 try: print '***********************' pid = os.fork() #这里会返回两次,所以下面的省略号会输出2次 print '......'

    操作系统 进程创建实验报告

    操作系统 进程创建实验报告 ...虽然父进程与子进程程序完全相同,但每个进程都有自己的程序计数器PC(注意子进程的PC开始位置),然后根据pid变量保存的fork( )返回值的不同,执行了不同的分支语句。

    linux-2.4.0内核源代码

    在内核代码 2.6.15.5中/kernel/fork.c第1255-1261中有如下代码:  1. p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);  2. if (!IS_ERR(p)) {  3. struct ...

Global site tag (gtag.js) - Google Analytics