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之后父进程才能被调度执行。假设有一种情况是,子进程在调用上面两个函数之前依赖于父进程的进一步动作,则会导致死锁,因为父进程在等待子进程独立,而子进程独立需要父进程执行。
运行结果如下:
那么,在一个进程创建子进程的时候会发生什么呢?
首先我们要了解,每个进程都有自己的虚拟地址空间,包括正文段、数据段、堆和栈四部分。而内核将会为这四个部分提供相应的物理内存,与进程的虚拟地址空间相映射。
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之后父进程才能被调度执行。假设有一种情况是,子进程在调用上面两个函数之前依赖于父进程的进一步动作,则会导致死锁,因为父进程在等待子进程独立,而子进程独立需要父进程执行。
相关文章推荐
- 【SSL】POODLE 漏洞细节分析与攻击构造
- python基础教程总结15——4 新闻聚合
- HTML5 新的API 窗口可视区 scrollIntoView dataset calssList
- easyui datagrid
- nyoj数数
- oc代码规范
- 网银在线的异步操作代码示意图
- python操作redis
- OpenCV视屏跟踪
- 命令行的基本使用方法(权限)
- php解析一个url
- MYSQL---设置存储引擎
- Spring DI 集合属性依赖注入
- [LeetCode] Simplify Path
- process launch failed: failed to get the task for process 1482
- iOS知识小集 第三期 (2015.06.30)
- 【图论】【二分图匹配】[HDU 2819]Swap
- 【bzoj3130】 SDOI2013费用流 最大流
- 比较Java Swing中三种注册事件的方法
- 创建fragment的适配器的时候,构造函数传入content参数