笔者一直试图从最基本的原理上去理解(甚至尝试原理性设计)一个服务器的架构,为此提出了一些问题。此外,笔者对 异步 I/O 也有不少学习。从几个方面学习了 vfork()
的用法。
本文纯粹记录一下。不过不同于其他资料的大段代码,本文更多地用文字和排版来尽可能清晰地说明。
本文地址:https://segmentfault.com/a/1190000010411198
Reference:
linux网络编程之socket(四):使用fork并发处理多个client的请求和对等通信p2p
fork和vfork的区别
fork 和 vfork
fork()
应该说是 UNIX 和类 UNIX 系统中最古老和最原始的创建子进程的系统调用了。甚至可以说,fork
不仅仅是创建子进程,而更多的是创建进程的主要手段。除了 init
进程之外,所以进程都是从 init
派生出来的。很多进程都可以是由 init
或者是 bash
的子进程,但这种情况下,我们已经不把它们作为子进程来看待了。
fork( )
简单一句话,fork()
的做法就是拷贝
父进程的上下文,然后再从父进程中分离出来。其实可能大部分程序员对 fork() 的用法是:创建子进程,然后父子进程使用 pipe 通信。
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( )
首先:vfork
与 fork
最大的区别是:子进程与父进程共享
相同的内存空间。换句话说,子进程对所有变量的操作,都会直接影响父进程——而这也就是很多人忌惮 vfork 的原因。为了避免这样的操作,vfork 有一个额外的与 fork 的不同:
其次:
vfork 之后得到的子进程,可以保证在调用 exit
或者 exec
系列调用之前,父进程都不会被执行。这是一个非常重要的特性,上述的两个特性,也就引出了 vfork 的应用场景。
Shell 调用系统命令
这里主要是应用了上文提到的第二个特性。详情请见我以前的文章。
跨进程计数
这源于我最近看的某个代码中的一个功能,那就是对各个进程中某个操作进行计数(也就是++
)。
这个时候 vfork 就派上用场了。但要注意,因为上面提到的 vfork 的隐患在,因此这非常考验程序设计艺术……呃,本来想要多写一些的,但是实际上,这样的一个场景,要怎么设计、怎么实现,还没好好思考好好考虑……所以先把思路放在这儿,继续好好学习吧。
(啥?进程互斥?放心,++ i
是一个原子操作,只需要一条机器指令就可以完成,不用考虑互斥)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。