本文由知名博主Dog250投稿Linux阅码场原创发表。
《有关微内核OS史上最透彻一篇 - 写于华为鸿蒙发布一周之际》
剪辑自腾讯云
1、微内核与宏内核
情景分析。
我就以普通的读取文件的read调用,以Minix为例,展示一下其行为:
这里需要解释的是,上述情景的每一个步骤看样子都在进行进程间通信(IPC)。确实,这就是微内核的特色之所在。
为了让系统核心的服务进程比如FS,MM更好的对每一个用户进程服务,在这些进程内部,均保存着系统所有进程的当前与本服务进程相关信息的快照。
FS进程请求SYS进程将从磁盘读取的内容复制到用户进程P的缓冲区,这一步是没有用户进程参与的,所以FS进程至少要知道用户进程P的内存信息元数据,这样才可以让内核态的SYS进程完成复制操作。
其实,可以这样理解,在微内核中,FS,MM这些服务进程的逻辑以及快照数据在宏内核中就是对应内核本身的,只不过它们的访问方式不同:
- 宏内核通过函数调用访问特定的逻辑和数据。
- 微内核通过IPC(进程间通信)访问特定的逻辑和数据。
作为对比,我们看一下宏内核Linux是如何完成与上面的情景IPC等价的操作步骤的:
看样子同样是read读取文件,微内核只是把宏内核的纵向通信换成了横向通信而已。然而这个一纵一横的背后,却隐藏着大不同。
- 纵向通信是实际发生的服务调用,即物理通信。
- 横向通信是对等层通信,即逻辑通信。【这也是影响性能的核心因素】采用横向通信方案,固然损害了性能,但得到的更多。
对照OSI以及TCP/IP分层模型,以上的说辞便再熟悉不过了。
如果只是为介绍微内核的运行原理和流程,我想到这里已经结束了,下面该可以愉快地看代码了。然而微内核在现实中并非可以和宏内核平起平坐,我们不使用它,不接受它,除了商业和生态因素导致的先入为主之外,还有一个更加重要的因素,即 性能。
2、性能对照
微内核性能太差了! 所有人都认为微内核性能差是因为IPC开销大,系统调用开销大所导致。当然,我也是这么认为。但是这只是事情的一面,事情还有另一面。
关于微内核的话题,无外乎就是:
- 内核精简,仅保留最核心的,其它全部给用户态。
- 模块化,可插拔,易于扩展。
- 安全,相互隔离,互不影响,可以采取不同的安全策略。
- 数学可证明。
- 性能...
- ...
好多话题网上一搜一箩筐,大多数都是形而上的套话,也不想在这里再次复制粘贴。
接下来我仅仅针对 性能 方面来对微内核进行一个定性的评估。另外,我尝试为微内核的性能表现做一番洗白。
a.通信模型
一说到对等层的逻辑通信,最终还是要落实到纵向的物理通信来落地,仍然以我比较熟悉的网络方面来类比。典型的TCP/IP的通信模型如下:
对于Minix微内核的read场景,和TCP/IP的模型几乎一致,在横向对等IPC之下,真正落地的是纵向通信。对应上述IPC横向逻辑通信过程的纵向实际流程如下图所示:
只要稍微一看这个图,第一个个感觉就是 这太TM繁琐了! 宏内核一个系统调用的事,微内核的IPC竟然要12个系统调用,而且仅仅是send/receive重复6对!如此设计性能显然好不了,意义何在呢?这个后面会讲。
对于微内核而言,只需要send,receive两个系统调用就够了,所有的系统功能都可以通过send/receive两个系统调用封装IPC消息来完成。
对应的,类似FS,MM这样的服务进程,就像普通的Web服务器守护进程那样,在一个大循环里等待receive就好了。
极简主义的典范,不是吗?这让我们可以联想到RISC处理器,看它们的汇编指令时,访存寻址只有load/store两个指令,也是极简主义的典范。
极简归极简,但是请看看上面那繁琐的流程,在宏内核中一个read系统调用的事,在Minix微内核中竟然要整整12个系统调用才能完成。回到上面的话题, 性能何在?效率何在?意义何在?
这是微内核饱受诟病的核心。
确实,纯传统IPC的方案,如此多的系统调用,开销是非常可观的。然而,通过以太网的发展史,我们或许可以看到曙光。
b.共享总线与交换机模式
我们看看早期的共享总线式以太网:
[注]工作方式:CSMA/CD(载波监听多路访问/冲突检测)先听后发,边听边发,冲突停止,随机延迟重发
。
我们对比一下宏内核:
也许是我们对宏内核太熟悉了,我们天天都在用Linux内核,不是吗?这导致我们可能看不到别的可能性,这个事实将我们蒙蔽在真相之下,使我们看不到宏内核其实是有很大问题的,比如多核的不可扩展性。【当然,Linux内核在多核扩展性方面一致在持续优化,这无可厚非,但是始终没有一个让人哇塞的方案...】
宏内核缺乏访问共享资源的有序仲裁机制,因此同步开销会非常大,最直接的后果就是宏内核随着处理器核心的增加而不可扩展。这个现状和早期以太网是多么相似, “以太网随着共享总线上接入的计算机数量的增加而性能陡降”。
所有使用内核服务的进程都在各自 隔离的上下文(现代操作系统之所以现代的原因) 中访问底层的共享资源,这是无法仲裁的根源。
说回以太网,当事情发展到交换式以太网的时候,问题解决了,因为冲突域坍缩到了交换机的背板总线,作为具备二层逻辑处理能力的交换机,它便可以进行必要的资源仲裁,比如排队以及队列的调度,实现数据包的存储转发:
数据包的有序排队代替了对总线的无序争抢,从而避免了冲突。
让我们看看微内核,与此是多么相似:
我们回看并思考一下以太网进化到交换式以太网时的情形。
面对交换式以太网,有人肯定会质疑, 原来数据包可以直接通过一根线飞到目的地,现在还要去交换机里走一遭,还有经由交换机逻辑的处理,多了这么多的步骤,如何能和曾经共享总线时一根线相比呢?
质疑者明显是忽略了CSMA/CD的开销。相比CSMA/CD的开销,交换机的处理开销与之做减法,最终化作了收益。【交换式以太网从此飞起,如今万兆以太网都已经是标配了】
类比以太网的发展历史,如果我们考虑多核处理器上宏内核的无序同步开销,在微内核中加入带有仲裁功能的服务进程后竟然大大降低甚至消失了,是不是有一丝的欣慰呢?
有了交换机之后,人们忘记了CSMA/CD(虽然还是要考试),那么若干年后,是不是也能忘掉spinlock【冲突原地等待】呢?
c.用户态协议栈
其实,在我们使用宏内核时也不是不明白专门进程处理专门事情的重要性,只是这种感觉来得比较自发罢了,并没有形成书面上的方法论。典型的例子就是最近几年非常风靡的Linux用户态协议栈。
为什么要构建用户态协议栈【权限-内核资源空间-并发】,那明显是因为内核协议栈性能低。用户态协议栈可以做到专核专用,进程上下文可以完全掌握数据包收包逻辑的全过程,更便于仲裁和协作。
如果用户态协议栈抽象成一个服务,这是不是就和微内核思想一致呢?
此外,除了协议栈,很多别的逻辑也纷纷往用户态搬迁,并且,在内核态本身,中断也越来越线程化了,以便统一参与内核的调度。微内核的思想一直在背后起着作用。
3、发展趋势
本来微内核的性能是软肋,结果被我说的好像成了它的优势,有点不太雅。但其实,我表达的更多的是微内核表现出来的一种潜力。站在多核心可扩展性角度,多核平台,无疑微内核的设计会更好,服务进程的仲裁可以让应用在多核平台无锁运行。
a.性能与扩展性争论
不过无论如何,我承认性能确实是微内核的软肋,特别是Minix的性能确实不咋地,但这也同样意味着微内核可以针对性能这个软肋集中地进行优化。业界公认的性能比较好的微内核当属QNX了,它被广泛部署在黑莓手机和汽车上,时间和存在可以证明,它守住了它承诺的。
在UNIX历史的长河中,一开始的系统就是奔着微内核的思想去的,比如最初的UNIX系统中会拥有一个专门负责调度和交换的swapper进程,直到现在,我们依然可以在UNIX/Linux中找到一个0号的swapper/idle进程,虽然它早就被淹没在宏内核之中,再也不行调度以及交换之事务了。
说到微内核的性能问题,我认为在经过了30多年的发展后,微内核,宏内核之间的性能差距已经大大缩小了,这个关于两种由于设计理念的不同导致的性能差异话题在计算机的发展史上,从来就没有停止过:
- 微内核和宏内核。
- 跨平台的Java语言和C语言。
- 跨平台的语言Java和跨语言的平台.NET。
- 解释型脚本Python和C语言。
- CISC体系结构和RISC体系结构。
- ...
但是无一例外,最终谁也吊打不了谁,收益必有代价,大多数的纷争最终走向了融合。
硬件技术的发展往往落后于软件技术的发展,但是硬件技术却是始终不断发展的,在此期间,软件技术往往陷入了两种理念的纷争而稍有停滞,最终硬件技术跟上,弥补软件性能的缺陷。甚至硬件可以针对软件的需求作出特殊的支持。
软件提出需求,硬件实现。如果微内核在理论上证明确实好,仅仅IPC是个瓶颈的话,直接从硬件上着手优化,岂不是更好吗?我想QNX,鸿蒙的设计应该也是这么考虑的吧。
软件硬件统一支持,我想微内核和宏内核之间最终也会是某种融合,我说的并非Windows作为混合内核的那种马赛克式的融合,而是熔炉式的融合。
具体到微内核性能差的根因,我们来聊一下IPC的优化。
b.IPC的优化
如果实现一个最基本最简单的IPC,那么内存拷贝就够了。这一般是0.1到1.0的做法。先让系统跑起来是最重要的, 完成比完美更重要 。【GNU Hurd内核的失败就是因为斯托曼太过理想主义,追求完美...反例则是李纳斯,追求完成。】
随着硬件技术的发展,随着系统对性能要求不断提高,IPC必然要不断优化,内存拷贝不再适用,共享内存则更好,最终,可能直接使用硬件的某种机制,比如寄存器,DMA等机制来帮助实现IPC。如果有硬件机制直接提供IPC的支持,问题就解决了。
这个过程非常类似sendfile/splice系统调用的设计过程。
sendfile/splice的设计
____起初,Web服务器需要先将文件拷贝到Web服务器内部buffer,然后再将buffer拷贝到用户的socket。这很类似传统的IPC方案。
____然而随着HTTP逐渐主宰互联网,几乎每一个Linux服务器上均部署有Web服务器,谁还能忍受两次拷贝的瓶颈,于是sendfile就呼之欲出了。sendfile仅仅提供一个外部把手,真正的数据并不需要拷贝到Web服务器的buffer,通过这个把手,数据可以从文件直通到socket。
____用的人多了,需求在了,问题自然也就解决了,而且是从底层根本上解决。微内核的IPC也会是这样的路子。
不过,目前还没有一个通用的使用在微内核上的IPC机制,相信QNX是有优化过的IPC的,但是不够通用,而Android系统的Binder够通用也还不错,但是它并不针对微内核。倒是非常希望能看看鸿蒙是怎么去优化的。
最后,我想说点儿有意思但是大家不怎么关注的东西。
- 内核态访问到空指针,系统就一定会崩溃panic吗? 并不是!只要内核不想panic就不会panic崩溃。只要错误不足以影响硬件状态,系统就不会无条件崩溃。内核之所以panic(或者蓝屏)是因为它认为这样最安全。就像用户态可以捕获SEGV信号一样,内核照样也能捕获空指针访问。然后恢复。前提是,你要确保即使发生了空指针错误,系统也是稳定的,即没有数据结构被破坏。而这个在宏内核里很难保证,因为所有东西都在一起。在微内核中,事情反而更简单些。首先,内核很小,出现问题的概率自然小,即便出现问题,也很容易知道如何去恢复而不会对其它结构造成损害;其次,很多重要的数据结构以及逻辑都在用户态的服务进程中,比如文件系统,网络协议栈,甚至驱动,这种服务进程拥有自己隔离的地址空间,即便是出现再严重的问题,也不会污染到操作系统其它的部分。
- 将Linux改成微内核有多难? 很难,但也不是不可能。首先,需要改变Linux特定代码和数据的地址空间结构,说到底就是将它们映射在用户态的空间并且给它们一套独立的页表。其次,为它们分配好task_t并处理好调度。最终,最难的是调用方式,这意味着你需要徒手将函数调用改成IPC,这让人觉得还不如基于Minix改呢。把Linux的好的代码移植进Linux??
- 微内核不被人知真的是因为它的性能差吗? 我倒是觉得这个跟Linux内核关系很大。Linux内核太出名了,以至于几乎所有人都认为作为一个操作系统内核就应该长得和Linux内核那样。这个和Linux内核开源是分不开的。除了Linux内核,很难再看到其它的流行内核是开源的。遵循POSIX的QNX作为一个类UNIX系统,它本身就是微内核的,如果QNX在1990i 年出现并且开源,可能大家眼里的操作系统内核就应该是微内核了。什么事情,只要说多了,那便是对的了。
总结
文章终于写完了, 本来想就此结束的,但还是想说点心里话。
最开头说了,希望能通过本文里的东西给大家带来点 技术 ,也确实,我专门为此在文中加入了源码分析。希望真的是把技术讲明白了,但毕竟这仅仅只是一篇文章,所以也就不能面面俱到,但即便是如此,我也希望本文至少可以作为关于微内核的科普,也就倍感欣慰了。
本文以鸿蒙操作系统作为引子,但也只是引子,我没见过鸿蒙系统真正的样子,所以我也不去过多猜测,没见过的东西,也没参与,事后去指点江山,总觉得缺了点什么。鸿蒙一出,发现网上瞬间出现这么多精通操作系统设计和开发的专家,原来大家以前真的都是潜龙勿用啊,如今变身,飞龙在天了。
也真是替华为感到遗憾,当年招聘的时候,有这么多专家都化名为鲲,潜在北冥不应召,如今一张PPT就让他们集体化而为鸟,怒而飞,改名为鹏了...
操作系统这门学科在即使计算机专业内部也并不算大众,因为它太底层,并且太复杂太无趣了,偏学术,让人觉得老套。没有漂亮产品妹子蹦蹦哒哒过来跟你对需求,也没什么班可加,完事后大家一起去啤酒撸串,更多的时候是你自己一个人大半夜在家里debug或者思考。
很难出成绩,这意味着加薪周期会比较长。
动辄以两年为单位的研发周期长,不符合BAT等大型互联网公司一年两次考核的快速迭代小步快跑理念,这意味着你可能连自己的工作都保不住。
多年不变的夯实的架构让操作系统领域显然没有更多新的东西可做,这就像TCP/IP协议栈一样,千年不变的底层架构显然让你的职业生涯并没有多少机会去拥抱变化,这显然和互联网理念是背道而驰的。
说白了,很多人对此并不感兴趣。
鸿蒙发布之后,突然发现很多人都是操作系统专家了,我想这是在蹭华为的热度而不是鸿蒙操作系统本身吧,因为操作系统自Windows 95,Windows XP之后,从来都没有再热过。不信你回家问问自己的家人,除了Windows 95/XP之外,还听说过哪个划时代的操作系统。
最后,想喷我站队华为的,先给在下演示一下如何在鸿蒙操作系统上写hello world再说吧,反正我没见过,等我看见了,如果它真的不如人所愿,我和你一起喷。打架不会,喷人还是有一套的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。