微内核领域的传说

IT 业和自然科学领域常说的「传说」一词来源于英文 myth ,是个负面的形容词,更接近「流言」、「谣言」的意思。如著名的电视节目《流言终结者》(mythblaster) 。也经常被翻译成「神话」,如著名的《人月神话》(Man Month Myth) 。这篇文章不是要贬低微内核 (micro-kernel) 这个概念本身,而是说人们对这个领域中的很多东西存在不小的误解。

Mac OS X 是微内核

OS X 的内核叫做 XNU ,是一个基于 Mach 和 BSD 的内核。因为 Mach 是最早的微内核,所以人们自然而然地认为 XNU 也是一个微内核。在软件领域「基于」这个概念是很模糊的。甚至一个系统只是从另一个系统的设计中借鉴了一些思路,也可以称为「基于」。虽然我相信 Windows NT 和 VMS 没有一丝一毫共同的 code base ,但还是认为前者「基于」后者。XNU 确实是继承了 Mach 和 BSD 不少的 code base ,但 XNU 抛弃了 Mach 最核心的设计选择 —— 微内核。所以即便二者有大量共同的 code base ,也只能勉强称为「基于」。

为什么继承了 Mach 大量 code 的衍生产品 XNU 反而抛弃了被继承者的核心设计?这要回头看 Mach 是如何开发的。开发一个全新的内核是件非常困难的事情。在内核的基本功能完成之前,开发者的工作几乎都是不可见的。Linus 在《 Just for Fun 》里回忆,到第一个 shell 被成功移植到 Linux kernel 上之前,他都看不到自己的工作有什么成效,突然( shell 移植完毕),似乎一切一下子完成了。这种最后突现的情形,对软件开发来说风险很大。因为错与对要到很晚才能揭晓。Mach 的开发采取了另一种渐进的、随时可以检验成果的方式:

  1. 取一个完整的 BSD 内核,改名叫 Mach 。
  2. 在这个内核上开发一套底层 IPC 机制。一般 UNIX 的 IPC 机制会利用内核模块的功能,比如 I/O ,内存管理,等等。这在微内核上是不推荐的,因为像内存管理或者 I/O 这类功能在理想的微内核上要作为用户态 (user-space) 服务。所以微内核的 IPC 非常底层。在 Mach 上是基于 message 和 port 的消息队列通信。
  3. 把原来的 BSD 内核各模块间通信(主要是方法调用)替换为 message/port 通信。
  4. 把已经改为通过 message/port 通信的模块从内核态 (kernel-space) 移出到用户态。

XNU 的做法是 undo 了第 4 步(有时连第 3 步也 undo 了),保留底层 IPC「机制」(mechanism) ,抛弃了 Mach 的微内核策略 (policy) 。通过这么做 XNU 拥有一个独特的优势 —— device driver 可以采用两种状态来运行,运行在用户态的时候利于开发和调试,运行在内核态的时候又不会引入微内核的开销。前者基本上只用来提高驱动开发的效率,实际发布版的 device driver 总是运行在内核态。所以,OS X 的内核 XNU 不是微内核。讨论微内核的成功与失败,性能高低等等问题,OS X 都不是一个适当的例子。

微内核解决了 Monolithic 内核不能解决的问题

往往认为微内核解决的最大问题是 monolithic 内核的复杂度和可维护性问题。可实际上微内核和 monolithic 内核在复杂度上并无显著差别,甚至后者还简单一些。从 Mach 和 OS X 内核 XNU 的例子可以看到,加上第 4 步,得到的就是微内核。去掉第 4 步,得到的就是 monolithic 内核。去掉第 3 步,得到的就是一个进一步简化了许多的 monolithic 内核。进一步去掉第 2 步回到原来的 BSD 内核,仍然是一个设计良好的内核,这也是 Mach 能在其上循序渐进的开发成功的原因。

其实,代码运行在内核态还是用户态不是构建其上的系统整体复杂度的决定因素,决定整体复杂度的关键在于各部分代码之间的功能边界是否清晰。微内核的作用在于强制约束了各个功能之间的边界,并且提供了有限的保护,也就是利用硬件 MMU 进行内存保护(但是这种保护并不真的总是有效,由于模块的 bug 造成的 crash ,常常并不能仅靠重启该模块来修复,而是必须重启整个微内核和所有服务,所以一个单独模块在内存保护之下 crash 所造成的危害并不见得一定比整个内核 crash 来的小)。这种约束的代价并不低廉。实际上,微内核本来的雄心是把 task schedule 和 IPC 之外的功能都放到用户态,但是由于硬件设计的限制(很多硬件其实都是按照 monolithic 内核的需求设计的),性能因素,以及内存保护能提供的实际错误恢复能力,实际中以微内核自许的产品 —— 比如 MINIX —— 一般也只能把 device driver 这样的外围模块放到用户态。提供强制的设计边界固然是好的,但是只有开发社区达到很大规模,这种需要付出代价的强制边界才能真正带来价值。小规模的开发社区更容易自律,从而更好的发挥 monolithic 内核本身结构精简的优势。而 device driver 的开发从来不是一个从业者人如潮涌的领域。

另一方面,monolithic 内核并不是真的铁板一块。Monolithic 的定义是不「强迫」开发者把模块放到用户态。「不强迫」不是「强迫」的对立物,而是后者的「超集」(superset) 。Monolithic 内核并不会阻止开发者把模块放到用户态,它所要求的是开发者自行开发一个小规模的内核态 stab 来负责把用户态模块的操作 relay 到内核。FUSE 就是一个在 monolithic 内核中实现用户态 driver 的例子。

所以微内核只是一个试图规约开发者的规范。它能解决的问题,并非 monolithic 内核所无法解决的,只不过侧重的灵活度角度不同。

微内核的效率很低下

这是个很有争议的问题。有些微内核将处理系统调用 (system call) 的模块也做成了用户态服务(仅仅是处理系统调用,而不是这些调用真正代表的功能。这里的处理系统调用模块只负责把用户态应用的请求分发到实际的功能服务)。即使是一次什么都不做的空系统调用,也要导致四次上下文切换 (context switch) 。

  1. 从发起系统调用的进程,切换到内核态。
    (只有栈和寄存器等状态切换,不需内存映射表的切换)
  2. 从内核态切换到处理系统调用的服务。
    (既有栈和寄存器等状态切换,也有内存映射表的切换)
  3. 从处理系统调用的服务切换回内核态。
    (只有栈和寄存器等状态切换,不需内存映射表的切换)
  4. 从内核态切换回发起系统调用的进程。
    (既有栈和寄存器等状态切换,也有内存映射表的切换)

常见的 monolithic 内核一般只需两次无需内存映射表的切换的上下文切换( 1 和 3 )。而且,由于处理系统调用的模块往往并非真正提供服务的模块,所以对微内核来说非空的系统调用会有另外的两到四次切换,而对 monolithic 来说只是一些普通的函数调用。作为 monolithic 内核 XNU 无需切换 2 和 4 ,但《地址空间划分》中介绍过,由于比较独特的地址空间,需要在切换 1 和 3 时切换内存映射表,所以开销会稍大。有 benchmark 测试曾经以 XNU 处理空系统调用慢于 Linux kernel 来说明微内核的效率低下,其实是误解了这个细节差异。

支持上面这个理论分析的实际性能数据大多都是在 Mach 微内核上得出。但是有人指出 Mach 是微内核的第一次尝试,设计和实现失误很多。比如很多的性能开销 (overhead) 其实发生在消息的合法性检验操作上,而非上下文切换。特别是用 Linux kernel 改写而成的 L4 微内核的支持者认为 L4 更好的精简了消息 IPC 机制,更多的利用了 x86 架构的独特硬件特性,性能和 monolithic 内核不相上下。

在我看来,这是「 Java 的性能已经和 C++ 不相上下」的另一个版本。不论怎么说,微内核引入的开销是客观存在的,更多细心精巧的设计也只能是降低这个开销,不可能完全消除。而微内核的安全保护,只有当内核的开发社团拥有一定规模的时候,才能体现出其价值。可是作为核心底层的模块,内核和 device driver 的开发永远是一个小团体的世界。特别是在 Mac 和 mobile 这类软硬件结合的生态体系重新获得成功,替代了以 multi-vendor 为特征的 PC 生态体系之后。

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

You are commenting using your WordPress.com account. Log Out /  更改 )

Google+ photo

You are commenting using your Google+ account. Log Out /  更改 )

Twitter picture

You are commenting using your Twitter account. Log Out /  更改 )

Facebook photo

You are commenting using your Facebook account. Log Out /  更改 )

w

Connecting to %s


%d 博主赞过: