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

笔者一直试图从最基本的原理上去理解(甚至尝试原理性设计)一个服务器的架构,为此提出了一些问题。此外,笔者对 异步 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( )

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

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

Shell 调用系统命令

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

跨进程计数

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

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

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

你可能感兴趣的:(linux,fork,进程,服务器,c)