您的位置:首页 > 其它

fork()、COW和vfork()的区别

2015-07-28 15:26 555 查看
一个进程可以通过调用fork()或者vfork()函数来创建一个新进程,调用进程被成为父进程,产生的新进程叫做子进程。而在调用(以fork()为例)fork()的时候,会产生两个返回值,一个是返回给父进程,另一个是返回给子进程。而用户可以通过返回值来判断哪个是父进程,哪个是子进程。在子进程中返回值为0,而在父进程中的返回值为子进程ID。这种安排是有理由的。一个进程可以有多个子进程,但是却只能有一个父进程,因此我们可以通过getppid()来获取父进程的ID,但是却没有函数用来获取子进程的ID,因此便将子进程的ID返回给父进程。下面的例子可以说明这一点:



运行结果如下:



那么,在一个进程创建子进程的时候会发生什么呢?

首先我们要了解,每个进程都有自己的虚拟地址空间,包括正文段、数据段、堆和栈四部分。而内核将会为这四个部分提供相应的物理内存,与进程的虚拟地址空间相映射。

fork()

p1调用fork()创建子进程p2时,p2拥有p1的副本,包括虚拟地址空间和相映射的物理内存,除了p1和p2共享正文代码段之外,其它的都是在物理级别上互相独立的。下面的例子显示出父、子进程拥有独立的数据段、共享正文代码段。



运行结果如下:



可以看出,两个进程的num值是独立的,但是代码段是共享的,都执行了if-else和printf语句。

COW(copy-on-write,写时复制)

一般地,创建子进程的目的并不是为了制造一个父进程的副本,它往往是用来exec一个新的程序,在启动新程序的时候,所有的数据代码都与父进程无关,因此都需要重新复制到新进程的地址空间中,这会大大浪费时间资源,因此,我们采用cow技术,暂时使子进程共用父进程的地址空间,并且将子进程的访问权限改为只读。如果当有进程(父进程或者子进程)试图修改其中的某个区域的话,则内核将为这个区域制作一个副本。简单来说,就是子进程拥有父进程虚拟地址空间的副本,并且暂时共享父进程的物理内存,对此内存只拥有只读权限,然后当有进程试图修改这些区域的时候,内核将为子进程制作一个该区域的副本。下面的程序可以解释:



上面代码中,父、子进程均未对num进行修改,运行结果如下:



从运行结果可以看出,在两个进程中,num的地址是一样的,即父、子进程共享同一段数据内存。再来,如果有进程对数据进行修改,如下代码:

vfork()

前面有提到,创建一个新进程的目的往往是用来exec一个新的程序,因此复制父进程的物理内存是完全没有必要的,于是就产生了cow技术。但是如果用户已经知道到了接下来新进程唯一要做的事情就是exec一个新程序,那么也没有必要实施cow技术了,毕竟cow技术需要复制父进程的虚拟地址空间,并且需要设置子进程的访问权限为只读,这写都是需要开销的。于是我们的vfork()更加绝对,它在调用exec之前(即独立更生之前),暂时和父进程公用一下虚拟地址空间,然后在exec一个新程序之后,在开辟它自己的空间。下面的程序说明父、子进程公用地址空间。



运行结果如下:



可以看出,父、子进程共享同一段区域,两者都进行了修改,修改操作并没有发生副本的生成,而是修改同一个内存区域。另外有一点,vfork总是保证让子进程先执行,在子进程调用exec或者exit之后父进程才能被调度执行。假设有一种情况是,子进程在调用上面两个函数之前依赖于父进程的进一步动作,则会导致死锁,因为父进程在等待子进程独立,而子进程独立需要父进程执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: