3.3 内核
本节介绍基于Solaris 系统的内核和Linux 的内核(按照年代顺序),它们的历史以及特点,以性能为着重点探讨两者的区别。UNIX 作为背景知识也会有所介绍。
现代内核之间有一些显著的区别,包括它们支持的文件系统(参见第8章)和它们所提供的观测框架(参见第4章)。还有就是它们的系统调用(syscall)界面、网络栈的架构、对实时的支持,以及CPU、磁盘和网络I/O 的调度。
表3.3 展示的是近期的一些内核版本,以及在它们系统man 页码第2 部分的系统调用的数目。这是一个粗略的比较,但是足以看出区别。
表3.3 内核版本和系统调用数目
其中有文档记录的系统调用,通常更多的系统调用是内核提供给操作系统软件私用的。除了内核之间的区别,还有一个随时间变化的规律:Linux 一直在增加系统调用,Solaris 一直在减少系统调用。
UNIX 初期只有20 个系统调用,而今天的Linux——作为一个嫡系——有上千个……我只是担心复杂性和事情发展的规模。
Ken Thompson,ACM 图灵百年纪念,2012
这两个内核确实越来越复杂,亦或增加了新的系统调用,亦或通过使用其他的内核界面,这个复杂性以不同的方式在用户空间有所体现。
3.3.1 UNIX
UNIX 是由Ken Thompson、Dennis Ritchie,以及其他AT&T 贝尔实验室的同仁,在1969年以及之后的岁月里开发的。关于UNIX 确切起源的描述在The UNIX Time-Sharing System [Ritchie 74]一书里:
当我们的一员(Thompson),不满意现有的计算机设备,发现了一个很少使用的PDP-7,便着手开始创造一个更适宜的系统环境,第一个版本便诞生了。
UNIX 的开发人员之前是工作在Multics(Multiplexed Information and Computer Services)操作系统上的。UNIX 的开发是作为一个轻量的多任务的操作系统和内核,命名自UNICS(UNiplexed Information and Computing Service),是Multics 的双关语。摘自UNIX Implementation [Thompson 78]:
内核只能是UNIX 的代码,不能出于用户的喜好而被替换。基于这个原因,内核应该尽可能少地做真正的决定。这不是意味着做一样的事情,用户可以有成千上万种选择。而是说,做一件事情只允许有一种方法,这种方法是所有可供选择的方法的最小公约数。
当时内核很小,但还提供了一些用于高性能的功能。进程有调度优先级,更高优先级的任务运行队列的延时会更小。为了效率,磁盘I/O 执行是用大块(512B),每个设备前都有置于内存中的缓冲区高速缓存,块会缓存在其中。空闲的进程会被交换到存储器里,让更忙的进程运行在主存里。而且,当然该系统是多任务处理的——允许多个进程并行运行,以提升吞吐量。
为了支持网络、多文件系统、换页,和其他我们现在认为是标准的东西,内核必然会增长。加上多个衍生,包括BSD、SunOS(Solaris),以及之后的Linux,内核的性能变得有竞争力,也推动着加入更多的功能和代码。
3.3.2 基于Solaris
Solaris 内核不仅是衍生自UNIX,甚至还有一些留下的代码直接来自最初UNIX 的内核。Solaris 开始于由Sun Microsystems 公司创造在1982年的SunOS。基于BSD,SunOS 保持了系统的小和紧凑,在Sun 工作站上运行得很好。到了20 世纪80年代后期,Sun 开发了新的操作系统功能,连同一些从BSD 和Xenix 来的功能,贡献到了AT&T 的UNIX System V 的Release 4(SVR4)中。随着SVR4 成为了UNIX 的标准,Sun 在此之上创造了一个新的内核和操作系统:SunOS 5。Sun 的销售称之为Solaris 2.0,并把之前的SunOS 更名为Solaris 1.0。不过,工程师还是在内核里保留着SunOS 的名字。
Sun 的内核开发,尤其是与性能相关的内容如下。
● NFS:NFS 协议让文件可以通过网络共享,并且可以透明地作为全局文件系统树的一部分(挂载)。现在广泛应用的NFS 是版本3 和版本4,这两个版本都引入了很多性能的提升。
● VFS:虚拟文件系统(VFS)是一个抽象,这个界面让多种文件系统很容易共存。Sun最初开发它是为了让NFS 和UFS 可以共存。VFS 的内容在第8章中有介绍。
● 页缓存:页缓存用于缓存虚拟内存页,自从它出现以来,就成为了多数操作系统的文件系统缓存的首选(ZFS ARC 是一个例外)。页缓存是SunOS 4 在重写虚拟内存时引入的,同时也支持共享页。关于更多页缓存的内容,参见第8章。
● 内存映射文件:能够用于减少文件I/O 的开销,是为SVR4 重写SunOS 的虚拟内存时引入的。
● RPC:远程过程调用接口。
● NIS:网络信息服务是一个简单的平面框架,用于在网络里共享信息,包括密码和hosts文件。它曾经广泛应用了多年,现在正让位于LDAP。
● CacheFS:缓存文件系统,Solaris 2.4(1994)引入,用于在访问缓慢的NFS 服务器时提升性能。之后,NFS 服务器的性能提升,CacheFS 便不再被广泛使用和考虑了。
● 完全抢占内核:一个早期的Sun 系统的变种是完全抢占内核,保证了包括实时工作在内的高优先级工作的低延时。
● 调度器级别:多个调度器级别可以对不同类型工作负载的性能做调整。包括分时(time-sharing,TS)、交互(interactive,IA)、实时(real-time,RT)、系统(system,SYS)、固定(fixed,FX)和公平份额调度器(fair-share scheduler,FSS)。参见第6章。
● 多处理器支持:在20 世纪90年代早期,Sun 大量地投入于多处理器操作系统的研发,开发出了既支持非对称多处理也支持对称多处理的内核(ASMP 和SMP)[Mauro 01]。
● slab 分配器:替代了SVR4 的buddy 分配器,这个内核slab 分配器通过让每个CPU 缓存预分配的缓冲区能更快地重用,提供了更好的性能。这一分配器类型,以及它的衍生,已经成为了操作系统的标准。
● crash 分析:Sun 开发了一套成熟的内核crash dump 分析框架,而且缺省是对整个系统开启的,还包含了modular debugger,可以用于crash dump、内核,以及应用程序的分析。
● M:N 线程调度:为了线程的高效调度目的,在线程和进程之间实现了一个对象,这个对象成为轻量进程(lightweight p rocess,LWP),区别于内核调度,LWP 可以有自己的用户级别的调度行为。后来发现Sun 的实现存在问题而且不值得那么复杂[Cantrill 96],在Solaris 9 中就被移除了,但是术语LWP 和一些数据结构还是留在了Solaris 的某些部分里。
● STREAMS 网络栈:Sun 在AT&T 的STREAMS 接口上搭建了自己的TCP/IP 网络栈,用以提供用户空间与内核空间的通信。最终,STREAMS 网络栈并没有随网络同步发展,在Solaris 10 之前,大多数的STREAMS 管道都被移除了。
● 64 位支持:Solaris 7 的内核(1998)提供了对64 位处理器的支持。
● 锁统计:Solaris 7 引入了锁性能的统计。
● MPSS:多种页面尺寸支持让OS 可以使用处理器提供的不同尺寸的内存页,包括大页(或者巨型页),以提升内存操作的效率。
● MPO:Solaris 9 加入了内存位置优化(Memory Placement Optimization),根据处理器架构(本地性)改进了内存分配的方法,可以显著地提高内存访问性能。
● 资源控制:一个用进程或进程组来对各种资源使用做限制的工具,称为projects(之后被Zones 使用)。
● FireEngine:针对Solaris 10 的一套高性能TCP/IP 栈的增强,包括vertical perimeters,对处理包的CPU 和内存的本地性做了提升,以及IP fanout,在CPU 之间做分散负载。
● Dtrace:一套静态和动态跟踪的框架和工具,可以对整个软件栈做近乎无限的观测,实时并且可以应用于生产环境。随Solaris 10 在2005年发布,DTrace 是第一次动态跟踪的广泛成功实现,已经移植到了其他操作系统,包括Mac OS X 和FreeBSD,现在是正在移植到Linux 的过程中。在第4章中会介绍DTrace。
● Zones:一种基于OS 的虚拟化技术,允许创建可以共享宿主内核的操作系统实例。在Solaris 1 0 发布,不过这个概念是由FreeBSD 的jails 在1998年首次完成的。相较于其他的虚拟化技术,这两个技术更轻量级且提供高的性能,参见第11章。
● Crossbow:一个提供高性能虚拟网络接口和网络带宽资源控制的架构。这一功能对于构建可靠的高性能的云至关重要。
● ZFS:ZFS 文件系统提供企业级的特性,随着Solaris 10 的update 1 一同发布,同时也是开源的。现在可为其他操作系统所用,并成为了许多文件服务器的基础文件系统。具体内容参见第8章。
上述许多的功能已经移植到了Linux,或者在Linux 上重新进行了实现,还有一些现在还在开发中。
迫于Linux 的压力,Sun 在2005年以OpenSolaris 项目开源了Solaris。直到2010年Oracle收购Sun 之前还是开源的,收购之后关于源代码更新的发布就停止了。OpenSolaris 最后的发布版本,是Solaris 11 开发版本的镜像。现在还有若干的操作系统基于illumos 内核,包括Joyent公司的SmartOS,在本书中的许多基于Solaris 的例子里都有用到SmartOS。
3.3.3 基于Linux
Linux 诞生于1991年,由Linus Torvalds 开发,当时是作为针对英特尔个人电脑的一款free(免费、自由)的操作系统。他在Usenet 的帖子里宣布了这个项目:
我正在做一个(免费的)操作系统(只是个爱好,不会变得很大很专业,不会像gnu 一样),针对的是386(486)AT 平台。从四月起就开始酝酿,现在马上就准备好了。我希望得到大家所有对minix 的喜欢/不喜欢的反馈,因为我的操作系统有点像它(文件系统是一样的物理布局(有实际的原因)还有一些其他事情)。
这个系统参考了MINIX 操作系统,MINIX 是针对小型计算机的free 的小型UNIX 版本。BSD 也试图提供一个free 的UNIX 版本,不过在当时存在法律上的问题。
Linux 内核开发的总体思路来自于许多前辈,如下。
● Unix(和Multics):操作系统层级、系统调用、多任务处理、进程、进程属性、虚拟内存、全局文件系统、文件系统权限、设备文件、缓冲区高速缓存。
● BSD:换页虚拟内存、按需换页、快文件系统(fast f ile s ystem,FFS)、TCP/IP 网络栈、套接字。
● Solaris:VFS、NFS、页缓存、统一页缓存、slab 分配器,以及ZFS 和DTrace(在进行中)。
● Plan 9:资源forks(rfork)、为进程间和线程(任务)间的共享设置不同的级别。
Linux 内核的功能,尤其是那些与性能相关的,包含如下。多数功能还标记了第一次引入Linux 时的内核版本。
● CPU 调度级别:各种先进的CPU 调度算法都有开发,包括调度域(2.6.7),对于非一致存储访问架构(NUMA)能做出更好的决策,参见第6章。
● I/O 调度级别:开发了不同的块I/O 调度算法,包括deadline(2.5.39)、anticipatory(2.5.75)和完全公平队列(CFQ)(2.6.6),参见第9章。
● TCP 拥塞:Linux 内核支持更新的TCP 拥塞算法,允许按需选择。此外,还有许多对TCP 的增强,参见第10章。
● Overcommit:有out-of-memory(OOM)killer,该策略用较少内存做更多的事情,参见第7章。
● Futex(2.5.7):fast user-space mutex 的缩写,用于提供高性能的用户级别的同步原语。
● 巨型页(2.5.36):由内核和内存管理单元(MMU)支持大型内存的预分配,参见第7章。
● Oprofile(2.5.43):研究CPU 使用和其他活动的系统剖析工具,内核和应用程序都适用。
● RCU(2.5.43):内核所提供的只读更新同步机制,支持伴随更新的多个读取的并发,提升了读取频繁的数据的性能和扩展性。
● epoll(2.5.46):可以高效地对多个打开的文件描述符做I/O 等待的系统调用,提升了服务器应用的性能。
● 模块I/O 调度(2.6.10):Linux 对调度块设备I/O 提供可插拔的调度算法,参见第9章。
● DebugFS(2.6.11):一个简单的非结构化接口,用该接口内核可以将数据暴露到用户级别,通常为某些性能工具所用。
● Cpusets(2.6.12):进程独占的CPU 分组。
● 自愿内核抢占(2.6.13):这个抢占过程,提供了低延时的调度,并且避免了完全抢占的复杂性。
● inotify(2.6.13):文件系统事件的监控框架。
● blktrace(2.6.17):跟踪块I/O 事件的框架和工具(后来迁移到了tracepoints 中)。
● splice(2.6.17):一个系统调用,将数据在文件描述符和管道之间快速移动,而不用经过用户空间。
● 延时审计(2.6.18):跟踪每个任务的延时状态,参见第4章。
● IO 审计(2.6.20):测量每个进程的各种存储I/O 统计。
● DynTicks(2.6.21):动态的tick,当不需要时(tickless),内核定时中断不会触发,这样可以节省CPU 的资源和电力。
● SLUB(2.6.22):新的slab 内存分配器的简化版本。
● CFS(2.6.23):完全公平调度算法,参见第6章。
● cgroups(2.6.24):控制组可以测量并限制进程组的资源使用。
● latencytop(2.6.25):观察操作系统的延时来源的仪器和工具。
● Tracepoints(2.6.28):静态内核跟踪点(也称静态探针)可以组织内核里的逻辑执行点,用于跟踪工具(之前是内核标记)。跟踪工具在第4章中介绍。
● perf(2.6.31):perf 是一套性能观测工具,包括CPU 性能计数器剖析、静态和动态跟踪。关于该内容的介绍,参见第6章。
● 透明巨型页(2.6.38):这是一个简化巨型(大型)内存页面使用的框架,参见第7章。
● Uprobes(3.5):用户级别软件动态跟踪的基础设施,为其他软件所用(perf、SystemTap,等等)。
● KVM:基于内核的虚拟机(Kernel-based Virtual Machine,KVM)技术是Qumranet 公司为Linux 开发的,该公司在2008年被Red Hat 公司收购。KVM 使得可以创建虚拟的操作系统实例,并运行虚拟机自己的内核,参见第11章。
上述的一部分功能,包括epoll 和KVM,都已经移植到了基于Solaris 的系统上,或者在基于Solaris 的系统上做了重新实现。
由于其具有广泛的设备驱动支持,Linux 还间接地贡献给了许多其他的操作系统,并且推动了这些系统的开源。
3.3.4 差异
虽然两个系统都是UNIX 的后代并且拥有着相同的操作系统理念,Linux 和基于Solaris 的内核还是有很多的不一样的地方,差异有大也有小,没办法很简洁地回答这一复杂的问题。
基于Linux 的系统的优势并非来自内核和操作系统本身,而是来自应用程序包的支持、设备驱动的支持、大型的社区,本质上就是它是开源的。多数基于Solaris 的系统也是开源的(Oracle Solaris 现在不是),但是它们并没有同样广泛的驱动支持(这对笔记本使用是个问题)。
基于Solaris 的系统提供ZFS 作为企业级的文件系统,DTrace 作为近乎无限的观测工具。虽然这些都正在移植到Linux,但它们在基于Solaris 的系统上直接就是成熟可用的,自从2003年就已经用在了生产环境中。Linux 取得了很多新的审计和跟踪的框架,提供扩展的观测能力(下一章会介绍),但是这些框架并没有缺省安装或者还没有被广泛启用。
除了这些主要的差异,这两个内核还有很多很小的不同点,尤其是在性能优化方面。要了解这些差异是否会影响你,分析预期的工作负载看看是否与之相关。
举一个小差异的例子,POSIX 的fadvise()调用当前在Linux 上是实现了的,但是在基于Solaris 的系统中却没有。这个调用是应用程序用来通知内核不要缓存与文件描述符相关的数据的,这样Linux 内核的缓存能更有效率,性能就提高了。下面是一个使用实例,来自MySQL数据库:
像这类小的差异会变化得很快,你看到本书时,上述这个特殊的问题可能已经在基于Solaris的系统内核里修正了。[3]
两个系统所提供的性能会有小的差异,取决于工作负载,最大的差异还是性能的观测工具,特别是对动态跟踪的支持。如果一个内核在你生产环境里可以让你找到10 倍乃至更高的性能提升,任何之前找到的10%左右的差别就看起来不那么重要了。
观测工具会在下一章介绍。