由于具有共同的祖先和相同的API,现代的Unix内核有很多共同的设计特点。没有任何意外,Unix通常是一个完整的、静态的二进制文件(聚内核)。这是指,它是一个运行在单个地址空间的单独的大的可执行镜像文件。Unix系统通过需要可分页的内存管理单元(MMU),这个硬件帮助系统加强内存保护并向每个进程提供单独虚拟地址空间。Linux历史上也需要MMU,但特殊的版本也可以运行在没有MMU的硬件环境上。这是一个巧妙的特性,允许Linux运行在没有MMU的小巧的嵌入式设备上,当然这是一个偏学术而不太实用的特性——因为在今天即使是简单的嵌入式系统也通常拥有高级的特性例如MMU。在本书里,我们聚焦在基于MMU的系统上。
聚内核与微内核
可以把内核分为主要的两个类别:聚内核与微内核(还有第三类,外核,主要存在于研究领域)。聚内核比其它两类的设计更简单,在1980之前所有内核都采用聚内核的方式。聚内核被实现为运行在单一的地址空间里的单一的进程。因此,这种内核通常以一个静态的二进制文件保存在磁盘中。所以内核的服务都存在并执行于一个大的内核地址空间里。在内核的内部进行通信显得直接了当,因为所有运行在内核态的代码都在相同的地址空间里:内核可以直接调用函数,就像用户空间的应用程序一样。这个模型的支持者们以聚内核的简洁和性能为理由。多大数Unix系统都采用聚内核的设计方法。
微内核则不是实现为单个的大的进程。而是把内核的功能分解成不同的进程,通常把这些进程称为服务。理想情况,只有在必要的时候服务才运行在特权模式下。其他服务都运行在用户空间里。所有这些服务,都被分解到不同的地址空间里,因此,像聚内核那样的直接的函数调用不再可能(指在多个服务间进行通信时)。替代地,微内核的通信通过消息传递:一套进程间通信(IPC)的机制被内嵌到系统中,各种服务通过在IPC之上发送消息来相互调用彼此的服务。把各种服务的分离可以防止一个服务的失败引起其他服务的失败。同样的,这种模块化的系统允许一个服务替换掉另一个服务。
由于IPC的机制会导致比平常的函数调用更多一些的开销,此外,它还包含在内核空间与用户空间之间进行上下文切换的开销,同时消息传递也会有一些延迟,所以微内核的吞吐量似乎不如采用简洁函数调用的聚内核。因此,所有实用的基于微内核的系统,现在都把大多数或所有服务放在内核空间,以避免频繁的上下文切换带来的开销,并潜在的支持直接的函数调用。Windows NT内核(被用于Windows XP,Vista,7)和Mach(被用于部分Mac OS X)就是微内核的例子。最新版的Windows NT和Mac OS X都不会在用户空间运行微内核的服务,它们都背离了微内核的设计初衷。
Linux属于聚内核,也就是说,整个Linux内核运行在内核态的单一的地址空间里。然而,Linux也从微内核中借鉴了很多好的想法:Linux推崇模块化的设计、可以抢点它自己(称为抢占式内核)、支持内核线程、可以动态加载二进制文件(内核模块)到内核镜像中。因此,Linux没有任何一个微内核设计带来的性能损耗:内核的代码都运行在内核态、直接的函数调用、没有消息传递的通信模式。虽然如此,Linux是模块化的、线程的、并且它自己是可调度的。实用主义再一次胜利。
随着Linus和其它开发者贡献到Linux内核,他们考虑如何更好的发展Linux而又不会疏忽掉它的Unix特性(更重要的是,Unix API)。因此,由于Linux不基于任何Unix变种,Linus和其他公司可以选择最好的解决方案来处理Unix面对的问题——或偶尔,发明新的解决方案。少量显著的差异存在于Linux和传统的Unix系统中:
尽管有这些差异,Linux还是保持了很强的Unix特质。