您的位置:首页 > 其它

fork函数浅析

2012-03-08 15:55 791 查看
包括:



fork函数简介

fork函数的两次返回和父子进程的执行顺序简介
fork()子进程与父进程之间的文件描述符问题

1 #include <stdio.h>                                                                                        
  2 #include <sys/types.h>
  3 #include <unistd.h>
  4 #include <stdlib.h>
  5 
  6 int main (void)
  7 {
  8     pid_t pid;
  9     char *message;
 10     int n;
 11     
 12     pid = fork();
 13 
 14     switch(pid)
 15     {
 16         case -1: 
 17             perror("fork error");
 18             exit(1);
 19         case 0:
 20             message = "I am the child process";
 21             n = 5;
 22             break;
 23         default:
 24             message = "I am the parent porcess";
 25             n = 5;
 26             break;
 27     }
 28     while(n>=0)
 29     {
 30         printf("%s",message);
 31         printf("my process id is %d\n",getpid());
 32         n--;
 33         sleep(1);
 34     }
 35 
 36     return 0;
 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("fork error");

exit(1);

case 0:

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

break;

default:

printf("I am the parent 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 parent process...

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



程序的运行结果(先输出Iam the parent process...,还是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个条目);但是关于标准输入,标准输出,标准错误,父子进程还是共享的。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: