研究一下 fork 的原理,而且还有 vfork 的一些使用场景

笔者一直试图从最基本的原理上去理解(甚至尝试原理性设计)一个服务器的架构,为此提出了一些问题。此外,笔者对 异步 I/O 也有很多学习。从几个方面学习了 vfork() 的用法。html

本文纯粹记录一下。不过不一样于其余资料的大段代码,本文更多地用文字和排版来尽量清晰地说明。linux

本文地址:http://www.javashuo.com/article/p-suwxuwld-cq.html程序员

Reference:

linux网络编程之socket(四):使用fork并发处理多个client的请求和对等通讯p2p
fork和vfork的区别编程

fork 和 vfork

fork() 应该说是 UNIX 和类 UNIX 系统中最古老和最原始的建立子进程的系统调用了。甚至能够说,fork 不只仅是建立子进程,而更多的是建立进程的主要手段。除了 init 进程以外,因此进程都是从 init 派生出来的。不少进程均可以是由 init 或者是 bash 的子进程,但这种状况下,咱们已经不把它们做为子进程来看待了。segmentfault


fork( )

简单一句话,fork()的作法就是拷贝父进程的上下文,而后再从父进程中分离出来。其实可能大部分程序员对 fork() 的用法是:建立子进程,而后父子进程使用 pipe 通讯。bash

fork 复制的上下文与父进程的关系

在实际使用中,确实就按照上文而言,fork() 以后的子进程对父进程是拷贝关系。也就是说,子进程对大多数变量的操做,都是不会影响父进程的。服务器

可是文件描述符等涉及操做系统层面的全局资源须要注意:这些仅仅是上下文在内存中的位置不一样,可是它们底层所指向的资源是共享的,操做时须要注意。不过事实上,这也是父子进程使用 pipe 互相通讯的原理基础。网络

fork 复制上下文的原理

我被问过一个问题:“fork() 调用时要拷贝上下文,这么作在大进程中调用的时候是否是效率很低?”架构

Linux 做为开源运动的集大成之做,显然不会那么蠢。事实上,调用 fork 以后,Linux 不会当即复制上下文,而是须要时才复制。因此咱们能够放心效率和内存占用。不过有一个例外,下文会很快说到。并发

至于 Linux 实现这一过程的原理,下面是个人推测,若是不对,请读者指出——

现代操做系统依赖于一个很重要的技术,就是内存映射,这须要硬件 CPU 支持 MMU。一个进程看到的内存地址,实际并非 RAM 的实际内存偏移值。操做系统会将进程实际使用的内存地址值映射到实际的硬件 RAM 中。若是系统强行或者意外访问了映射表中未注册的地址值区间,那么硬件 MMU 模块会发出一个底层硬件中断。操做系统监听这个中断,就能够知道发生了非法内存访问,也就是segmentation fault

有了 MMU 这么强大的东西,怎么不用呢?fork 以后,对于那些子进程尚未使用到的父进程内存内容,操做系统能够先放一放,不急着在内存中建立副本。若是子进程处理中出现了越界访问,那么操做系统彻底能够判断一下该内存是否父进程的内存内容。若是不是,则抛出段错误;若是是,就复制内存内容而且建立内存映射——这就是子进程真正复制父进程内容的时刻。

至于程序段?那是只读内容,压根不用拷贝,共享同一段实际内存就好了。

因此就像前文所说,咱们不用担忧子进程的内存浪费问题。可是例外的状况就是:好比进程 A fork 了子进程 B,子进程 B 的实际内存使用不多,于是增长的实际内存很少。可是一旦进程 A 退出了,那么进程 A 所占有的那些内存不能回收呀,由于操做系统咋知道进程 B 要不要使用 A 的内存内容?

换句话说,调用 fork() 的进程仍是尽可能节省内存,或者说尽可能即用即还。


vfork( )

首先:
vforkfork 最大的区别是:子进程与父进程共享相同的内存空间。换句话说,子进程对全部变量的操做,都会直接影响父进程——而这也就是不少人忌惮 vfork 的缘由。为了不这样的操做,vfork 有一个额外的与 fork 的不一样:

其次:
vfork 以后获得的子进程,能够保证在调用 exit 或者 exec 系列调用以前,父进程都不会被执行。这是一个很是重要的特性,上述的两个特性,也就引出了 vfork 的应用场景。

Shell 调用系统命令

这里主要是应用了上文提到的第二个特性。详情请见我之前的文章

跨进程计数

这源于我最近看的某个代码中的一个功能,那就是对各个进程中某个操做进行计数(也就是++)。

这个时候 vfork 就派上用场了。但要注意,由于上面提到的 vfork 的隐患在,所以这很是考验程序设计艺术……呃,原本想要多写一些的,可是实际上,这样的一个场景,要怎么设计、怎么实现,还没好好思考好好考虑……因此先把思路放在这儿,继续好好学习吧。

(啥?进程互斥?放心,++ i 是一个原子操做,只须要一条机器指令就能够完成,不用考虑互斥)