在目前的操作系统教学中,已有一系列优秀的操作系统教材,例如 William Stallings 的《Operating Systems Internals and Design Principles》,Avi Silberschatz 、 Peter Baer Galvin 和 Greg Gagne 的《Operating System Concepts》, Remzi H. Arpaci-Dusseau 和 Andrea C. Arpaci-Dusseau 的《Operating Systems: Three Easy Pieces》等。
然而,从我们自 2000 年以来的教学实践来看,某些经典教材对操作系统的概念和原理很重视,但还有如下一些问题有待思考:
- 原理与实践脱节:与操作系统的具体实现而言,操作系统的原理与概念相对过于抽象。目前的一些教材缺乏在“操作系统的原理与概念”和“操作系统的设计与实现”之间建立关联关系的桥梁,使得二者之间存在较大的鸿沟。这导致学生即使知道了操作系统的概念,还只能停留在“纸上谈兵”的阶段,依然不知如何实现一个操作系统。另外,学生在完成设计与实现操作系统的实验过程中,容易“一叶障目,不见泰山”,陷入到硬件规范、汇编代码、数据结构、编程优化等细节中,不知这些细节与操作系统概念的关系,缺少全局观和系统思维,难以与课堂上老师讲解的操作系统基本概念对应起来。
- 缺少历史发展的脉络:以史为鉴,可以知兴替。操作系统的概念和原理是从实际操作系统设计与实现的历史发展过程中,随着计算机硬件和应用需求的变化,从无到有逐步演进而产生的,有其发展的历史渊源和规律。但目前的大部分教材只提及当前主流操作系统的概念和原理,有“凭空出现”的感觉,学生并不知道这些内容出现的前因后果,只知道“How”,而不知道“Why”。而且操作系统发展史上的很多设计思路和实践方法起起伏伏,不断演进,它们并没有过时,而是以新的形态出现。如操作系统远古阶段的 LibOS 设计思路在当前云计算时代重新焕发青春,成为学术机构和各大互联网企业探索的新热点。
- 忽视硬件细节或用复杂硬件:很多教材忽视或抽象硬件细节,使得操作系统概念难以落地,学生了解不到软硬件是如何具体协同运行的。部分教材把复杂的 x86 处理器作为操作系统实验的硬件参考平台,缺乏对当前快速发展的 RISC-V 等精简体系结构的实验支持,使得学生在操作系统实验中可能需要花较大代价了解相对繁杂的 x86 硬件细节,编程容易产生缺陷(bug),影响操作系统实验的效果,以及对操作系统核心概念的掌握。
这些现存问题增加了学生学习和掌握操作系统的难度。我们尝试通过如下方法来解决上面三个问题,达到缓解学生的学习压力,提升学习兴趣,能在一个学期内比较好地掌握操作系统的目标。
具体而言,为应对“原理与实践脱节”的问题,我们强调了实践先行,实践引领原理的理念。MIT 教授 Frans Kaashoek 等师生设计实现了基于 UNIX v6 的 xv6 教学操作系统用于每年的本科操作系统课的实验中,并在课程讲解中把原理和实验结合起来,在国际上得到了广泛的认可,也给了我们很好的启发。经过十多年的操作系统教学工作,我们认为:对一位计算机专业的本科生而言,设计实现一个操作系统(包括CPU)有挑战但可行,前提是这样的操作系统要简洁小巧,能体现操作系统中最基本的核心思想,并能把操作系统各主要部分的原理与概念关联起来,形成一个整体。而且还需要丰富的配套资源,比如对操作系统的整体框架、核心算法、关键组件之间的联系等的分析文档、配套的图示和视频讲解、能够自动测试操作系统功能的测试用例和测试环境、能展现操作系统逐步编写过程的在线版本管理环境,以及逐步递进的综合性在线实验环境等,这样就能够让学生很方便地通过实践来加深对操作系统原理和概念的理解,并能让操作系统原理和概念落地。
为应对“缺少历史发展的脉络”的问题,我们重新设计操作系统实验和教学内容,按照操作系统的历史发展过程来设立每一章的内容,每一章会围绕操作系统支持应用的某个核心目标来展开,形成相应的软硬件基本知识点和具体实践内容。同时建立与每章配套的多个逐步递进且相对独立的小实验,每个实验会形成一个独立的操作系统,体现了操作系统的一个微缩的发展历史,并可从中归纳总结出操作系统相关的概念与原理。这样可以在教学中引导学生理解操作系统的这些概念和原理是如何一步一步演进的。表面上看,这样会要求同学了解多个不同的操作系统,增加了同学的学习负担。但其实每个实验中的操作系统都是在前一个实验的操作系统上的渐进式扩展,同学只需理解差异的部分即可。而且学生通过分析不同操作系统对应用支持能力和对应实现上的差异,可以更加深入地理解相关操作系统概念与原理出现的前因后果。也许有同学认为讲解历史上的操作系统太过时了。但我们认为:技术可以过时,思想值得传承。
为应对“忽视硬件细节或用复杂硬件”的问题,我们在硬件(x86, ARM, MIPS, RISC-V 等)和编程语言(C, C++, Go, Rust 等)选择方面进行了多年尝试。在 2017 年把 复杂 x86 架构换为 简洁 RISC-V 架构,作为操作系统实验的硬件环境,降低了学生学习硬件细节的负担。在 2018 年引入 Rust 编程语言作为操作系统实验的可选编程语言之一,通过Rust语言特有的编译时内存/并发安全检查、强类型静态分析、高级数据结构支持、抽象封装支持、内嵌汇编和丰富的第三方库,来提高编程效率、降低调试成本,从而减少了用C语言编程和对硬件操作出现较多运行时缺陷的情况。使得学生以相对较小的开发和调试代价进行操作系统实验。同时,我们把操作系统的概念和原理直接对应到程序代码、硬件规范和操作系统的实际执行中,加强学生对操作系统内涵的实际体验和感受。
所以本书的目标是以简洁的 RISC-V 基本架构为底层硬件基础,根据上层应用从小到大的需求,按 OS 发展的历史脉络,逐步讲解如何设计实现能满足“从简单到复杂”应用需求的多个“小”操作系统。并且在设计实现操作系统的过程中,逐步解析操作系统各种概念与原理的知识点,做到有“理”可循和有“码”可查,最终让同学通过操作系统设计与实现来深入地掌握操作系统的概念与原理。
在具体撰写过程中,第零章是对操作系统的一个概述,让同学对操作系统的历史、定义、特征等概念上有一个大致的了解。后面的每个章节体现了操作系统的一个微缩的历史发展过程,即从对应用由简到繁的支持角度出发,每章会讲解如何设计一个可运行应用的操作系统,满足应用的阶段性需求。从而同学可以通过配套的操作系统设计实验,了解如何从一个微不足道的“小”操作系统,根据应用需求,添加或增强操作系统功能,逐步形成一个类似 UNIX 的相对完善的“小”操作系统。每一步都小到足以让人感觉到易于掌控。而在每一步结束时,你都有一个支持不同应用执行的“小”操作系统。另外,通过足够详尽的测试程序和自动测试框架,可以随时验证同学实现的操作系统在每次更新后是否正常工作。由于实验的代码规模和实现复杂度在一个逐步递增的可控范围内,同学可以结合对应操作系统实验的原理/概念分析,来建立操作系统概念原理和实际实现的对应关系,从而能够通过操作系统实验的实践过程来加强对理论概念的理解,并通过理论概念来进一步指导操作系统实验的实现与改进。
在你开始阅读与实践本书讲解的内容之前,你需要决定用什么编程语言来完成操作系统实验。你可以选择你喜欢的编程语言和在你喜欢的CPU上来实现操作系统。我们推荐的编程语言是 Rust ,我们推荐的架构是 RISC-V。
本书提供了哪些“小”操作系统?
我们按照操作系统的发展历史,设计了如下一些逐步进化的“小”操作系统
- LibOS: 让APP与HW隔离,简化应用访问硬件的难度和复杂性
- BatchOS: 让APP与OS隔离,加强系统安全,提高执行效率
- Multiprog & Time-sharing OS: 让APP共享CPU资源
- Address Space OS: 隔离APP访问的内存地址空间,限制APP之间的互相干涉,提高安全性
- Process OS: 支持APP动态创建新进程,增强进程管理和资源管理能力
- Filesystem OS:支持APP对数据的持久保存
- IPC OS:支持多个APP进程间数据交互与事件通知
- Thread & Coroutine OS:支持线程和协程APP,简化切换与数据共享
- SyncMutex OS:在多线程APP中支持对共享资源的同步互斥访问
- Device OS:提高APP的I/O效率和人机交互能力,支持基于外设中断的串口/块设备/键盘/鼠标/显示设备
如何基于本书学习操作系统?
这取决于你想学习操作系统的目标,这里主要分为两类:
- 掌握基本原理为主,了解具体实现为辅(一般学习)
- 理解式学习方式:逐章阅读与实践,阅读分析应用,并通过分析应用与OS的动态执行过程,掌握OS原理。
- 掌握操作系统实现和原理为主(深入学习)
- 构造式学习:在理解式学习方式基础上,进一步分析源码,逐步深入了解每个OS的内部增量实现,并且参考/基于这些小OS,扩展部分OS功能,通过测试用例,从而同时掌握操作系统实现和原理。
编程语言与指令集选择
目前常见的操作系统内核都是基于 C 语言的,为何要推荐 Rust 语言?
- 事实上, C 语言就是为写 UNIX 而诞生的。Dennis Ritchie 和 KenThompson 没有期望设计一种新语言能帮助高效地开发复杂与并发的操作系统逻辑(面向未来),而是希望用一种简洁的方式来代替难以使用的汇编语言抽象出计算机的行为,便于编写控制计算机硬件的操作系统(符合当时实际情况)。
- C 语言的指针既是天使又是魔鬼。它灵活且易于使用,但语言本身几乎不保证安全性,且缺少有效的并发支持。这导致内存和并发漏洞成为当前基于 C 语言的主流操作系统的噩梦。
- Rust 语言具有与 C 一样的硬件控制能力,且大大强化了安全编程和抽象编程能力。从某种角度上看,新出现的 Rust 语言的核心目标是解决 C 的短板,取代 C 。所以用 Rust 写 OS 具有很好的开发和运行体验。
- 用 Rust 写 OS 的代价仅仅是学会用 Rust 编程。
目前常见的指令集架构是 x86 和 ARM ,为何要推荐 RISC-V ?
- 目前为止最常见的指令集架构是 x86 和 ARM ,它们已广泛应用在服务器、台式机、移动终端和很多嵌入式系统中。由于它们的通用性和向后兼容性需求,需要支持非常多(包括几十年前实现)的软件系统和应用需求,导致这些指令集架构越来越复杂。
- x86 后向兼容的策略确保了它在桌面和服务器领域的江湖地位,但导致其丢不掉很多已经比较过时的硬件设计,让操作系统通过冗余的代码来适配各种新老硬件特征。
- x86 和 ARM 在商业上都很成功,其广泛使用使得其 CPU 硬件逻辑越来越复杂,且不够开放,不能改变,不是开源的,难以让感兴趣探索硬件的学生了解硬件细节,在某种程度上让CPU成为了一个黑盒子,并使得操作系统与硬件的交互变得不那么透明,增加了学习操作系统的负担。
- 从某种角度上看,新出现的 RISC-V 的核心目标是灵活适应未来的 AIoT (人工智能物联网, AI + IoT)场景,保证基本功能,提供可配置的扩展功能。其开源特征使得学生都可以深入CPU的运行细节,甚至可以方便地设计一个 RISC-V CPU。从而可帮助学生深入了解操作系统与硬件的协同执行过程。
- 编写面向 RISC-V 的 OS 的硬件学习代价仅仅是你了解 RISC-V 的 Supervisor 特权模式,知道 OS 在 Supervisor 特权模式下的控制能力。