Chinaunix首页 | 论坛 | 博客
  • 博客访问: 655114
  • 博文数量: 171
  • 博客积分: 2246
  • 博客等级: 大尉
  • 技术积分: 1574
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-31 11:45
文章分类

全部博文(171)

文章存档

2018年(3)

2017年(4)

2015年(1)

2014年(20)

2013年(57)

2012年(86)

分类: C/C++

2012-09-08 17:23:11

FROM:
声明:这篇文章属转载,文章中有图片缺失,但这对于阅读无大碍。 

fock 的意思是复制进程, 就是把当前的程序再加载一次不同之处在,加载后,所有的状态和当前进程是一样的(包括变量)。 fork 不像线程需提供一个函数做为入口, fork后,新进程的入口就在 fock的下一条语句一个现存进程调用f o r k函数是U N I X内核创建一个新进程的唯一方法(这并不适用于前节提及的交换进程、i n i t进程和页精灵进程。这些进程是由内核作为自举过程的一部分以特殊方式创建的)。 
##########################################################
#i nclude  
#i nclude  

pid_t  fork(void);  返回:子进程中为0,父进程中为子进程I D,出错为-1 
##########################################################
由f o r k创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程 I D。将子进程I D返回给父进程的理由是:因为一个进程的子进程可以多于一个,所以没有一个函数使一个进程可以获得其所有子进程的进程I D。f o r k使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用g e t p p i d以获得其父进程的进程I D (进程ID 0总是由交换进程使用,所以一个子进程的进程I D不可能为0 )。子进程和父进程继续执行f o r k之后的指令。子进程是父进程的复制品例如,子进程获得父进程数据空间、堆和栈的复制品注意,这是子进程所拥有的拷贝父、子进程并不共享这些存储空间部分如果正文段是只读的,则父、子进程共享正文段(见7 . 6节)。现在很多的实现并不做一个父进程数据段和堆的完全拷贝,因为在 f o r k之后经常跟随着e x e c。作为替代,使用了在写时复制( C o p y - O n - Write, COW)的技术。这些区域由父、子进程共享,而且内核将它们的存取许可权改变为只读的。如果有进程试图修改这些区域,则内核为有关部分(典型的是虚存系统中的“页”)做一个拷贝。B a c h〔1 9 8 6〕的9 . 2节和L e ff l e r等〔1 9 8 9〕的5 . 7节对这种特征做了更详细的说明。 
实例程序8 - 1例示了f o r k函数。
如果执行此程序则得到:
$ a . o u t 
a write to stdout 
before fork
pid = 430, glob = 7, var = 89  子进程的变量值改变了
pid = 429, glob = 6, var = 88    父进程的变量值没有改变
$ a.out > temp.out 
$ cat temp.out 
a write to stdout 
before fork
pid = 432, glob = 7, var = 89 
before fork
pid = 431, glob = 6, var = 88
        一般来说,在f o r k之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。如果要求父、子进程之间相互同步,则要求某种形式的进程间通信。在程序 8 - 1中,父进程使自己睡眠2秒钟,以使子进程先执行。但并不保证 2秒钟已经足够,在8 . 8节说明竟争条件时,还将谈及这一问题及其他类型的同步方法。在 1 0 . 6节中,在f o r k之后将用信号使父、子进程同步。
注意,程序8 - 1中f o r k与I / O函数之间的关系。回忆第3章中所述,w r i t e函数是不带缓存的。因为在f o r k之前调用w r i t e,所以其数据写到标准输出一次。但是,标准 I / O库是带缓存的。回忆一下5 . 1 2节,如果标准输出连到终端设备,则它是行缓存的,否则它是全缓存的。当以交互方式运行该程序时,只得到p r i n t f输出的行一次,其原因是标准输出缓存由新行符刷新。但是当将标准输出重新定向到一个文件时,却得到p r i n t f输出行两次。其原因是,在f o r k之前调用了p r i n t f一次,但当调用f o r k时,该行数据仍在缓存中,然后在父进程数据空间复制到子进程中时,该缓存数据也被复制到子进程中。于是那时父、子进程各自有了带该行内容的缓存。在 e x i t之前的第二个p r i n t f将其数据添加到现存的缓存中。当每个进程终止时,其缓存中的内容被写到相应文件中。 
程序8-1   fork函数实例 


文件共享
对程序8 - 1需注意的另一点是:在重新定向父进程的标准输出时,子进程的标准输出也被重新定向。实际上,f o r k的一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程每个相同的打开描述符共享一个文件表项(见图3 - 3 )。考虑下述情况,一个进程打开了三个不同文件,它们是:标准输入、标准输出和标准出错。在从f o r k返回时,我们有了如图8 - 1中所示的安排。这种共享文件的方式使父、子进程对同一文件使用了一个文件位移量。考虑下述情况:一个进程f o r k了一个子进程,然后等待子进程终止。假定,作为普通处理的一部分,父、子进程都向标准输出执行写操作。如果父进程使其标准输出重新定向 (很可能是由s h e l l实现的),那么子进程写到该标准输出时,它将更新与父进程共享的该文件的位移量。在我们所考虑的例子中,当父进程等待子进程时,子进程写到标准输出;而在子进程终止后,父进程也写到标准输出上, 
并且知道其输出会添加在子进程所写数据之后。如果父、子进程不共享同一文件位移量,这种形式的交互就很难实现。 



如果父、子进程写到同一描述符文件,但又没有任何形式的同步(例如使父进程等待子进程),那么它们的输出就会相互混合(假定所用的描述符是在 f o r k之前打开的)。虽然这种情况是可能发生的(见程序8 - 1),但这并不是常用的操作方式。在f o r k之后处理文件描述符有两种常见的情况:(1) 父进程等待子进程完成。在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读、写操作的任一共享描述符的文件位移量已做了相应更新。(2) 父、子进程各自执行不同的程序段。在这种情况下,在f o r k之后,父、子进程各自关闭它们不需使用的文件描述符,并且不干扰对方使用的文件描述符。这种方法是网络服务进程中经常使用的。除了打开文件之外,很多父进程的其他性质也由子进程继承:* 实际用户I D、实际组I D、有效用户I D、有效组I D。* 添加组I D。* 进程组I D。* 对话期I D。* 控制终端。* 设置-用户- I D标志和设置-组- I D标志。* 当前工作目录。 
* 根目录。* 文件方式创建屏蔽字。* 信号屏蔽和排列。* 对任一打开文件描述符的在执行时关闭标志。* 环境。* 连接的共享存储段。* 资源限制。父、子进程之间的区别是:* fork的返回值。* 进程I D。* 不同的父进程I D。* 子进程的t m s _ u t i m e , t m s _ s t i m e , t m s _ c u t i m e以及t m s _ u s t i m e设置为0。* 父进程设置的锁,子进程不继承。* 子进程的未决告警被清除。* 子进程的未决信号集设置为空集。其中很多特性至今尚末讨论过,我们将在以后几章中对它们进行说明。使f o r k失败的两个主要原因是:( a )系统中已经有了太多的进程(通常意味着某个方面出了问题),或者( b )该实际用户I D的进程总数超过了系统限制。回忆表2 - 7,其中C H I L D _ M A X规定了每个实际用户I D在任一时刻可具有的最大进程数。f o r k有两种用法:(1) 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待委托者的服务请求。当这种请求到达时,父进程调用 f o r k,使子进程处理此请求。父进程则继续等待下一个服务请求。(2) 一个进程要执行一个不同的程序。这对 s h e l l是常见的情况。在这种情况下,子进程在从f o r k返回后立即调用e x e c (我们将在8 . 9节说明e x e c )。某些操作系统将( 2 )中的两个操作( f o r k之后执行e x e c )组合成一个,并称其为s p a w n。U N I X将这两个操作分开,因为在很多场合需要单独使用 f o r k,其后并不跟随e x e c。另外,将这两个操作分开,使得子进程在f o r k和e x e c之间可以更改自己的属性。例如I / O重新定向、用户I D、信号排列等。在第1 4章中有很多这方面的例子。
自己试验得结论: 
#i nclude
#i nclude
int main(){       
 int  pid;
        int pid2;
        //printf("%d ",getppid());
        printf("=============\n");
        printf("LLLLLLLLLLLLL");
        if((pid=fork())==0){
                printf("This is the child process:%d\n",pid);
        }else{
                if((pid2=fork())==0)
                        printf("This is another child process:%d\n ",pid2); 
               else
                       printf("This is the parent process:%d  %d\n",pid,pid2);
        }
        printf("--------\n");
        return 1;
}输出结果为 
=============LLLLLLLLLLLLLThis is the child process:0--------LLLLLLLLLLLLLThis is another child process:0 --------LLLLLLLLLLLLLThis is the parent process:22180  22181-------- 

#i nclude
#i nclude
int main(){
        int pid;
        int pid2;
        //printf("%d ",getppid());
        //printf("=============\n");
        printf("LLLLLLLLLLLLL");
        if((pid=fork())==0){
                printf("This is the child process:%d\n",pid);
        }else{
                if((pid2=fork())==0)
                        printf("This is another child process:%d\n ",pid2);
                else
                        printf("This is the parent process:%d  %d\n",pid,pid2);
        }
       printf("--------\n");
        return 1;

输出结果为: 
LLLLLLLLLLLLLThis is the child process:0--------LLLLLLLLLLLLLThis is another child process:0 --------LLLLLLLLLLLLLThis is the parent process:22212  22213-------- 

原因是  
子进程同时复制父进程的缓存内容 printf()进行行缓存 在exit 之后才将数据写回标准输出文件中 
   
对fork函数的体会 进程的创建 创建一个进程的系统调用很简单.我们只要调用fork函数就可以了. 
#i nclude   
pid_t fork(); 当一个进程调用了fork以后,系统会创建一个子进程.这个子进程和父进程不同的地方只有他的进程ID和父进程ID,其他的都是一样.就象符进程克隆(clone)自己一样.当然创建两个一模一样的进程是没有意义的.为了区分父进程和子进程,我们必须跟踪fork的返回值. 当fork掉用失败的时候(内存不足或者是用户的最大进程数已到)fork返回-1,否则fork的返回值有重要的作用.对于父进程fork返回子进程的ID,而对于fork子进程返回0.我们就是根据这个返回值来区分父子进程的. 父进程为什么要创建子进程呢?前面我们已经说过了Linux是一个多用户操作系统,在同一时间会有许多的用户在争夺系统的资源.有时进程为了早一点完成任务就创建子进程来争夺资源. 一旦子进程被创建,父子进程一起从fork处继续执行,相互竞争系统的资源.有时候我们希望子进程继续执行,而父进程阻塞,直到子进程完成任务.这个时候我们可以调用wait或者waitpid系统调用。 
阅读(867) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~