fork函数简介

包括:

 

fork函数简介

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

fork()子进程与父进程之间的文件描述符问题

 

 

[cpp]view plaincopyprint?

 

1         1 #include <stdio.h>                                                                                         

2         2 #include <sys/types.h> 

3         3 #include <unistd.h> 

4         4 #include <stdlib.h> 

5         5  

6         6 intmain (void

7         7 { 

8         8    pid_t pid; 

9         9     char*message; 

10    10     int n; 

11    11      

12    12     pid = fork(); 

13    13  

14    14     switch(pid) 

15    15     { 

16    16         case -1:  

17    17             perror("forkerror"); 

18    18             exit(1); 

19    19         case 0: 

20    20             message = "Iam the child process"

21    21             n = 5; 

22    22             break

23    23         default

24    24             message = "Iam the parent porcess"

25    25             n = 5; 

26    26             break

27    27     } 

28    28     while(n>=0) 

29    29     { 

30    30         printf("%s",message); 

31    31         printf("myprocess id is %d\n",getpid()); 

32    32         n--; 

33    33         sleep(1); 

34    34     } 

35    35  

36    36     return 0; 

37    37 }  

 

注释(1):关于getpid()getppid()

                       getdid()放在一个进程中可以获得此进程的pid。

                       getppid()放在一个进程中可以获得此进程的父进程的pid。

注释(2):

                       fork函数的返回值是pid_t类型的(返回值-1是错误、返回值0是表示子进程、返回值大于0表示父进程实际值是父进程的子进程pid值), fork后产生的子进程和父进程的pid也是pid_t类型的 用getpid()能知道。

关于系统的广度和深度进程树的实现:

 

fork函数简介

#include <sys/types.h> 

#include <unistd.h> 

/*

功能:复制进程

参数:无

返回值:  成功:  父进程:返回子进程id

                子进程:返回0

         失败:  返回-1

*/ 

pid_t fork(void); 

由fork创建的新进程被称为子进程(childprocess)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程id。将子进程id返回给父进程的理由是:因为一个进程的子进程可以多于一个,没有一个函数使一个进程可以获得其所有子进程的进程id。对子进程来说,之所以fork返回0给它,是因为它随时可以调用getpid()来获取自己的pid;也可以调用getppid()来获取父进程的id。(进程id0总是由交换进程使用,所以一个子进程的进程id不可能为0)。

fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,子进程拥有父进程当前运行到的位置(两进程的程序计数器pc值相同,也就是说,子进程是从fork返回处开始执行的),但有一点不同,如果fork成功,子进程中fork的返回值是0,父进程中fork的返回值是子进程的进程号,如果fork不成功,父进程会返回错误。可以这样想象,2个进程一直同时运行,而且步调一致,在fork之后,他们分别作不同的工作,也就是分岔了。这也是fork为什么叫fork的原因

至于那一个最先运行,可能与操作系统(调度算法)有关,而且这个问题在实际应用中并不重要,如果需要父子进程协同,可以通过原语的办法解决。

 

一个fork例子

 

#include <unistd.h> 

#include <sys/types.h> 

#include <stdio.h> 

int main(void) 

    pid_t pid; 

    pid=fork(); 

    switch (pid) 

    { 

    case -1: 

        perror("forkerror"); 

        exit(1); 

    case 0: 

        printf("I am thechild process, my process id is %d/n", getpid()); 

        break; 

    default: 

        printf("I am theparent process, my process id is %d/n", getpid()); 

        break; 

    } 

     return 0; 

}  

要搞清楚fork的执行过程,就必须先弄清楚操作系统中的“进程(process)”概念。一个进程,主要包含三个元素:
o.一个可以执行的程序;
o.和该进程相关联的全部数据(包括变量,内存空间,缓冲区等等);
o.程序的执行上下文(executioncontext)。

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

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

(假设父进程一直占据CPU,实际情况很可能不一样)父进程继续执行,操作系统对fork的实现,使这个调用在父进程中返回刚刚创建的子进程的pid(一个正整数),所以下面的swtich语句中执行了default分支(case-1,case0分支都不满足)。所以输出Iam the parentprocess...

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

 

程序的运行结果(先输出Iam the parentprocess...,还是Iam the parent process...)不可预见,与操作系统实际运行情况有关!

 

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函数中,它会有一个标志性的变量,根据其不同取值,来决定先让谁执行,比如子进程先执行然后再把父进程插入到队列中,具体位置我也没研究清楚,简单来说就是在内核的实现过程中,父子进程的执行顺序还是由程序本身来决定的,但是到了用户态看起来就是随机的了。

以上是我个人在看了资料后的理解和总结,可能在细节方面有些问题,欢迎大家指正!

fork()子进程与父进程之间的文件描述符问题

在C程序中,文件由文件指针或者文件描述符表示。ISOC的标准I/0库函数(fopen,fclose,fread, fwrite, fscanf, fprintf等)使用文件指针,UNIX的I/O函数(open,close,read, write,ioctl)使用文件描述符。下面重点来说下,文件描述符是如何工作的。

文件描述符相当于一个逻辑句柄,而open,close等函数则是将文件或者物理设备与句柄相关联。句柄是一个整数,可以理解为进程特定的文件描述符表的索引。先介绍下面三个概念,后面讲下open、close等操作以后,文件和文件描述符产生什么关系,以及fork后文件描述符的继承等问题。

文件描述符表:用户区的一部分,除非通过使用文件描述符的函数,否则程序无法对其进行访问。对进程中每个打开的文件,文件描述符表都包含一个条目。
系统文件表:为系统中所有的进程共享。对每个活动的open,它都包含一个条目。每个系统文件表的条目都包含文件偏移量、访问模式(读、写、or读-写)以及指向它的文件描述符表的条目计数。
内存索引节点表:对系统中的每个活动的文件(被某个进程打开了),内存中索引节点表都包含一个条目。几个系统文件表条目可能对应于同一个内存索引节点表(不同进程打开同一个文件)。
1、举例:执行myfd= open( "/home/lucy/my.dat",O_RDONLY); 以后,上述3个表的关系原理图如下:

 

 

系统文件表包含一个偏移量,给出了文件当前的位置。若2个进程同时打开一个文件(如上图A,B)做读操作,每个进程都有自己相对于文件的偏移量,而且读入整个文件是独立于另一个进程的;如果2个进程打开同一个文件做写操作,写操作是相互独立的,每个进程都可以重写另一个进程写入的内容。
如果上面进程在open以后又执行了close()函数,操作系统会删除文件描述符表的第四个条目和系统文件表的对应条目(若指向它的描述符表唯一),并对内存索引节点表条目中的计数减1,如果自减以后变为0,说明没有其他进程链接此文件,将索引节点表条目也删除,而这里进程B也在open这个文件,所以索引节点表条目保留。

2、文件描述符的继承

通过fork()创建子进程时,子进程继承父进程环境和上下文的大部分内容的拷贝,其中就包括文件描述符表。

(1)对于父进程在fork()之前打开的文件来说,子进程都会继承,与父进程共享相同的文件偏移量。如下图所示(0-1-2表示 标准输入-输出-错误):

 

 

 

系统文件表位于系统空间中,不会被fork()复制,但是系统文件表中的条目会保存指向它的文件描述符表的计数,fork()时需要对这个计数进行维护,以体现子进程对应的新的文件描述符表也指向它。程序关闭文件时,也是将系统文件表条目内部的计数减一,当计数值减为0时,才将其删除。
(2)相反,如果父进程先进程fork,再打开my.dat,这时父子进程关于my.dat的文件描述符表指向不同的系统文件表条目,也不再共享文件偏移量(fork以后2个进程分别open,在系统文件表中创建2个条目);但是关于标准输入,标准输出,标准错误,父子进程还是共享的。

 

 

 

linux中fork()函数详解

2012年02月03日 09:35 来源:chinaitlab 作者:ChinaITLab 编辑:刘亚琼

        【IT168 技术】  一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

  一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

  我们来看一个例子:

  [cpp] view plaincopy

  /*

  * fork_test.c

  * version 1

  * Created on: 2010-5-29

  * Author: wangth

  */

  #include

  #include

  int main ()

  {

  pid_t fpid; //fpid表示fork函数返回的值

  int count=0;

  fpid=fork();

  if (fpid < 0)

  printf("error infork!");

  else if (fpid == 0) {

  printf("i am thechild process, my process id is %d/n",getpid());

  printf("我是爹的儿子/n");//对某些人来说中文看着更直白。

  count++;

  }

  else {

  printf("i am theparent process, my process id is %d/n",getpid());

  printf("我是孩子他爹/n");

  count++;

  }

  printf("统计结果是: %d/n",count);

  return 0;

  }

  运行结果是:

  i am the child process,my process id is 5574

  我是爹的儿子

  统计结果是: 1

  i am the parentprocess, my process id is 5573

  我是孩子他爹

  统计结果是: 1

  在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……

  为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

  1)在父进程中,fork返回新创建子进程的进程ID;

  2)在子进程中,fork返回0;

  3)如果出现错误,fork返回一个负值;

  在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

  引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.

  fork出错可能有两种原因:

  1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。

  2)系统内存不足,这时errno的值被设置为ENOMEM。

  创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

  每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。

  fork执行完毕后,出现两个进程,

  有人说两个进程的内容完全一样啊,怎么打印的结果不一样啊,那是因为判断条件的原因,上面列举的只是进程的代码和指令,还有变量啊。

  执行完fork后,进程1的变量为count=0,fpid!=0(父进程)。进程2的变量为count=0,fpid=0(子进程),这两个进程的变量都是独立的,存在不同的地址中,不是共用的,这点要注意。可以说,我们就是通过fpid来识别和操作父子进程的。iude

  还有人可能疑惑为什么不是从#include处开始复制代码的,这是因为fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了int count=0;fork只拷贝下一个要执行的代码到新的进程。(自己添加:因为FORK是复制产生一个新的进程,因此新的进程与旧的的进程之间的上下文,如寄存器上下文等是一致的,也就是说两个进程的变量值,PC指针值也是一样的,因此两个进程都是在同一个位置开始运行)

  二、fork进阶知识

  先看一份代码:

  [cpp] view plaincopy

  /*

  * fork_test.c

  * version 2

  * Created on: 2010-5-29

  * Author: wangth

  */

  #include

  #include

  int main(void)

  {

  int i=0;

  printf("i son/pappid pid fpid/n");

  //ppid指当前进程的父进程pid

  //pid指当前进程的pid,

  //fpid指fork返回给当前进程的值

  for(i=0;i<2;i++){

  pid_t fpid=fork();

  if(fpid==0)

  printf("%d child%4d %4d %4d/n",i,getppid(),getpid(),fpid);

  else

  printf("%d parent%4d %4d %4d/n",i,getppid(),getpid(),fpid);

  }

  return 0;

  }

  运行结果是:

  i son/pa ppid pid fpid

  0 parent 2043 3224 3225

  0 child 3224 3225 0

  1 parent 2043 3224 3226

  1 parent 3224 3225 3227

  1 child 1 3227 0

  1 child 1 3226 0

 

  这份代码比较有意思,我们来认真分析一下:

  第一步:在父进程中,指令执行到for循环中,i=0,接着执行fork,fork执行完后,系统中出现两个进程,分别是p3224和p3225(后面我都用pxxxx表示进程id为xxxx的进程)。可以看到父进程p3224的父进程是p2043,子进程p3225的父进程正好是p3224。我们用一个链表来表示这个关系:

  p2043->p3224->p3225

  第二步:假设父进程p3224先执行,当进入下一个循环时,i=1,接着执行fork,系统中又新增一个进程p3226,对于此时的父进程,p2043->p3224(当前进程)->p3226(被创建的子进程)。

  对于子进程p3225,执行完第一次循环后,i=1,接着执行fork,系统中新增一个进程p3227,对于此进程,p3224->p3225(当前进程)->p3227(被创建的子进程)。从输出可以看到p3225原来是p3224的子进程,现在变成p3227的父进程。父子是相对的,这个大家应该容易理解。只要当前进程执行了fork,该进程就变成了父进程了,就打印出了parent。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值