操作系统导论

978-7-115-50823-2
作者: [美] 雷姆兹·H.阿帕希杜塞尔( Remzi H. Arpaci-Dusseau) [美]安德莉亚·C.阿帕希杜塞尔(Andrea C. Arpaci-Dusseau)
译者: 王海鹏
编辑: 陈冀康

图书目录:

详情

这是一本关于现代操作系统的书。主题分为三个主要的概念:虚拟化、并发性和持久性。全书介绍了所有现代系统的主要组件,包括调度、虚拟内存管理、磁盘和I/O子系统、文件系统,甚至一个简短的介绍分布式系统。本书内容全面,非常易于学习,很适合用于老师的教学和高校学生自学。

图书摘要

版权信息

书名:操作系统导论

ISBN:978-7-115-50823-2

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著     [美] 雷姆兹•H.阿帕希杜塞尔( Remzi H. Arpaci-Dusseau)

     [美] 安德莉亚•C.阿帕希杜塞尔(Andrea C. Arpaci-Dusseau)

译    王海鹏

责任编辑 陈冀康

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


Simplified Chinese translation copyright ©2019 by Posts and Telecommunications Press

ALL RIGHTS RESERVED

Operating Systems: Three Easy Pieces by Remzi H Arpaci-Dusseau ,Andrea C Arpaci-Dusseau,ISBN 9781985086593.

Copyright © 2018 by Remzi H Arpaci-Dusseau and Andrea C Arpaci-Dusseau.

本书中文简体版由作者授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。

版权所有,侵权必究。


这是一本关于现代操作系统的书。全书围绕虚拟化、并发和持久性这3个主要概念展开,介绍了所有现代系统的主要组件(包括调度、虚拟内存管理、磁盘和I/O子系统、文件系统 )。

本书共50章,分为3个部分,分别讲述虚拟化、并发和持久性的相关内容。本书大部分章节均先提出特定的问题,然后通过书中介绍的技术、算法和思想来解决这些问题。作者以对话形式引入所介绍的主题概念,行文诙谐幽默却又鞭辟入里,力求帮助读者理解操作系统中虚拟化、并发和持久性的原理。

本书内容全面,并给出了真实可运行的代码(而非伪代码),还提供了相应的练习,适合高等院校相关专业教师教学和高校学生自学。


欢迎阅读本书!我们希望你阅读本书时,就像我们撰写它时一样开心。本书的英文书名为《Operating Systems:Three Easy Pieces》,这显然是向理查德·费曼(Richard Feynman)针对物理学主题创作的、最了不起的一套讲义[F96]致敬。虽然本书不能达到这位著名物理学家设定的高标准,但也许足够让你了解什么是操作系统(以及更一般的系统)。

本书围绕3个主题概念展开讲解:虚拟化(virtualization)、并发(concurrency)和持久性(persistence)。对于这些概念的讨论,最终延伸到讨论操作系统所做的大多数重要事情。希望你在这个过程中体会到一些乐趣。学习新事物很有趣,对吧?

每个主要概念在若干章节中加以阐释,其中大部分章节都提出了一个特定的问题,然后展示了解决它的方法。这些章节很简短,尝试(尽可能地)引用作为这些想法真正来源的源材料。我们写这本书的目的之一就是厘清操作系统的发展脉络,因为我们认为这有助于学生更清楚地理解过去是什么、现在是什么、将来会是什么。在这种情况下,了解香肠的制作方法几乎与了解香肠的优点一样重要。

我们在整本书中采用了几种结构,值得在这里介绍一下。

无论何时,在试图解决问题时,我们首先要说明最重要的问题是什么。我们在书中明确提出关键问题(crux of the problem),并希望通过本书其余部分提出的技术、算法和思想来解决。

在许多地方,我们将通过显示一段时间内的行为来解释系统的工作原理。这些时间线(timeline)是理解的本质。如果你知道会发生什么,例如,当进程出现页故障时,你就可以真正了解虚拟内存的运行方式。如果你理解日志文件系统将块写入磁盘时发生的情况,就已经迈出了掌握存储系统的第一步。

整本书中有许多“补充”和“提示”,为主线讲解增添了一些趣味性。“补充”倾向于讨论与主要文本相关的内容(但可能不是必要的);“提示”往往是一般经验,可以应用于所构建的系统。

在整本书中,我们使用最古老的教学方法之一——对话(dialogue)。这些对话用于介绍主要的主题概念,并经常地复习这些内容。这也让我们得以用更幽默的方式写作。好吧,你觉得它们是有用还是幽默,完全是另一回事。

在每一个主要部分的开头,我们将首先呈现操作系统提供的抽象(abstraction),然后在后续章节中介绍提供抽象所需的机制、策略和其他支持。抽象是计算机科学各个方面的基础,因此它在操作系统中也是必不可少的。

在所有的章节中,我们尝试使用真实代码(real code),而非伪代码(pseudocode)。因此书中几乎所有的示例,你应该能够自己输入并运行它们。在真实系统上运行真实代码是了解操作系统的最佳方式,因此建议你尽可能这样做。

在本书的各个部分,我们提供了一些作业(homework),确保你进一步理解书中的内容。其中许多作业都是对操作系统的一些模拟(simulation)程序。你应该下载作业,并运行它们,以此来测验自己。作业模拟程序具有以下特征:通过给它们提供不同的随机种子,你可以产生几乎无限的问题,也可以让模拟程序为你解决问题。因此,你可以一次又一次地自测,直至很好地理解了这些知识。

本书最重要的附录是一组项目(project),可供你通过设计、测试和实现自己的代码,来了解真实系统的工作原理。所有项目(以及上面提到的代码示例)都是使用C编程语言(C programming language)[KR88]编写的。C是一种简单而强大的语言,是大多数操作系统的基础,因此值得添加到你的工具库中。附录中含有两种类型的项目(请参阅在线附录中的想法)。第一类是系统编程(system programming)项目。这些项目非常适合那些不熟悉C和UNIX,并希望学习如何进行底层C编程的人。第二类基于在麻省理工学院开发的实际操作系统内核,称为xv6 [CK+08]。这些项目非常适合已经有一些C的经验并希望深入研究操作系统的学生。在威斯康星大学,我们以 3 种不同的方式开课:系统编程、xv6编程,或两者兼而有之。

这门课程很适合15周的学期,因此授课教师可以在合理的深度范围内讲授大部分主题。如果是10周的学期,那么可能需要从每个部分中删除一些细节。还有一些章节是关于虚拟机监视器的,我们通常会在学期的某个时候插入这些章节,或者在虚拟化部分的结尾处,抑或在接近课程结束时作为补充。

本书中的并发主题比较特别。它是许多操作系统书籍中靠前的主题,而在本书中是直到学生了解了CPU和内存的虚拟化之后才开始讲解的。根据我们近15年来教授本课程的经验,学生很难理解并发问题是如何产生的,或者很难理解人们试图解决它的原因。那是因为他们还不了解地址空间是什么、进程是什么,或者为什么上下文切换可以在任意时间点发生。然而,一旦他们理解了这些概念,那么再引入线程的概念和由此产生的问题就变得相当容易,或者至少比较容易。

我们尽可能使用黑板(或白板)来讲课。在着重强调概念的时候,我们会将一些主要的想法和例子带进课堂,并在黑板上展示它们。讲义有助于为学生提供需要解决的具体问题。在着重强调实践的时候,我们就将笔记本电脑连上投影仪,展示实际代码。这种授课风格特别适用于并发的内容以及所有的讨论部分。在这些部分中,教师可以向学生展示与其项目相关的代码。我们通常不使用幻灯片来讲课,但现在我们已经为那些喜欢这种演示风格的人提供了一套教学PPT。

如果你想要任何这些教学辅助材料,请给contact@epubit.com.cn发电子邮件。

最后一个请求:如果你使用免费在线章节,请直接访问作者网站。这有助于我们跟踪使用情况(过去几年中,本书英文版下载超过100万次!),并确保学生获得最新和最好的版本。

如果你是读这本书的学生,那么我们很荣幸能够提供一些材料来帮助你学习操作系统的知识。我们至今还能够回想起我们使用过的一些教科书(例如,Hennessy和Patterson的著作[HP90],这是一本关于计算机架构的经典著作),并希望这本书能够成为你美好的回忆之一。

你可能已经注意到,这本书英文版的在线版本是免费的,并且可在线获取[1]。有一个主要原因:教科书一般都太贵了。我们希望,这本书是新一波免费材料中的第一本(指电子版),以帮助那些寻求知识的人—— 无论他们来自哪个国家,或者他们愿意花多少钱购买一本书。

我们也希望,在可能的情况下,向你指出书中大部分材料的原始资料—— 多年来的优秀论文和人物,他们让操作系统领域成为现在的样子。想法不会凭空产生,它们来自聪明勤奋的人(包括众多图灵奖获得者[2]),因此如果有可能,我们应该赞美这些想法和人。我们希望这样做能有助于更好地理解已经发生的变革,而不是说好像我们写这本书时那些思想一直就存在一样[K62]。此外,也许这样的参考文献能够鼓励你深入挖掘,而阅读该领域的著名论文无疑是良好的学习方法之一。

这里感谢帮助我们编写本书的人。重要的是,你的名字可以出现在这里!但是,你可能需要向我们提供帮这里感谢帮助我们编写本书的人。在这里列出他们的名字,以表达谢意!也请本书的读者向我们提供一些阅读反馈,帮助完善本书。我们将会因此而把您的名字加入致谢名单。

到目前为止,提供帮助的人包括Abhirami Senthilkumaran*, Adam Drescher* (WUSTL), Adam Eggum, Aditya Venkataraman, Adriana Iamnitchi and class (USF), Ahmed Fikri*, Ajaykrishna Raghavan, Akiel Khan, Alex Wyler, Ali Razeen (Duke), AmirBehzad Eslami, Anand Mundada, Andrew Valencik (Saint Mary’s), Angela Demke Brown (Toronto), B. Brahmananda Reddy (Minnesota), Bala Subrahmanyam Kambala, Benita Bose, Biswajit Mazumder (Clemson), Bobby Jack, Björn Lindberg, Brennan Payne, Brian Gorman, Brian Kroth, Caleb Sumner (Southern Adventist), Cara Lauritzen, Charlotte Kissinger, Chien-Chung Shen (Delaware)*, Christoph Jaeger, Cody Hanson, Dan Soendergaard (U. Aarhus), David Hanle (Grinnell), David Hartman, Deepika Muthukumar, Dheeraj Shetty (North Carolina State), Dorian Arnold (New Mexico), Dustin Metzler, Dustin Passofaro, Eduardo Stelmaszczyk, Emad Sadeghi, Emily Jacobson, Emmett Witchel (Texas), Erik Turk, Ernst Biersack (France), Finn Kuusisto*, Glen Granzow (College of Idaho), Guilherme Baptista, Hamid Reza Ghasemi, Hao Chen, Henry Abbey, Hrishikesh Amur, Huanchen Zhang*, Huseyin Sular, Hugo Diaz, Itai Hass (Toronto), Jake Gillberg, Jakob Olandt, James Perry (U. Michigan-Dearborn)*, Jan Reineke (Universität des Saarlandes), Jay Lim, Jerod Weinman (Grinnell), Jiao Dong (Rutgers), Jingxin Li, Joe Jean (NYU), Joel Kuntz (Saint Mary’s), Joel Sommers (Colgate), John Brady (Grinnell), Jonathan Perry (MIT), Jun He, Karl Wallinger, Kartik Singhal, Kaushik Kannan, Kevin Liu*, Lei Tian (U. Nebraska-Lincoln), Leslie Schultz, Liang Yin, Lihao Wang, Martha Ferris, Masashi Kishikawa (Sony), Matt Reichoff, Matty Williams, Meng Huang, Michael Walfish (NYU), Mike Griepentrog, Ming Chen (Stonybrook), Mohammed Alali (Delaware), Murugan Kandaswamy, Natasha Eilbert, Nathan Dipiazza, Nathan Sullivan, Neeraj Badlani (N.C. State), Nelson Gomez, Nghia Huynh (Texas), Nick Weinandt, Patricio Jara, Perry Kivolowitz, Radford Smith, Riccardo Mutschlechner, Ripudaman Singh, Robert Ordòñez and class (Southern Adventist), Rohan Das (Toronto)*, Rohan Pasalkar (Minnesota), Ross Aiken, Ruslan Kiselev, Ryland Herrick, Samer Al-Kiswany, Sandeep Ummadi (Minnesota), Satish Chebrolu (NetApp), Satyanarayana Shanmugam*, Seth Pollen, Sharad Punuganti, Shreevatsa R., Sivaraman Sivaraman*, Srinivasan Thirunarayanan*, Suriyhaprakhas Balaram Sankari, Sy Jin Cheah, Teri Zhao (EMC), Thomas Griebel, Tongxin Zheng, Tony Adkins, Torin Rudeen (Princeton), Tuo Wang, Varun Vats, William Royle (Grinnell), Xiang Peng, Xu Di, Yudong Sun, Yue Zhuo (Texas A&M), Yufui Ren, Zef RosnBrick, Zuyu Zhang。特别感谢上面标有星号的人,他们的改进建议尤其重要。

此外,衷心感谢Joe Meehean教授(Lynchburg)为每一章所做的详细注解,感谢Jerod Weinman教授(Grinnell)和他的全班同学提供的令人难以置信的小册子,感谢Chien-Chung Shen教授(Delaware)的细致阅读和建议,感谢Adam Drescher(WUSTL)的细致阅读和建议,感谢Glen Granzow(College of Idaho)提供详细的评论和建议,感谢Michael Walfish(NYU)详细的改进建议。上述所有人都给予本书作者巨大的帮助,优化了本书的内容。

另外,非常感谢这些年来参加537课程的数百名学生。特别是2008年秋季课程的学生,鼓励我们第一次以书面形式写下了这些讲义(他们厌倦了没有任何类型的教科书可读——有进取心的学生!),然后不吝称赞,让我们继续前行(一位同学在那一年的课程评估中喜不自禁地说:“老天爷!你们完全应该写一本教科书!” )。

我们也非常感谢那些参加xv6项目实验课程的少数人,这个实验课程大部分现已纳入主要的537课程。2009年春季班的Justin Cherniak,Patrick Deline,Matt Czech,Tony Gregerson,Michael Griepentrog,Tyler Harter,Ryan Kroiss,Eric Radzikowski,Wesley Reardan,Rajiv Vaidyanathan和Christopher Waclawik。2009年秋季班的Nick Bearson,Aaron Brown,Alex Bird,David Capel,Keith Gould,Tom Grim,Jeffrey Hugo,Brandon Johnson,John Kjell,Boyan Li,James Loethen,Will McCardell,Ryan Szaroletta,Simon Tso和Ben Yule。2010年春季班的Patrick Blesi,Aidan Dennis-Oehling,Paras Doshi,Jake Friedman,Benjamin Frisch,Evan Hanson,Pikkili Hemanth,Michael Jeung,Alex Langenfeld,Scott Rick,Mike Treffert,Garret Staus,Brennan Wall,Hans Werner,Soo -Young Yang和Carlos Griffin。

虽然没有直接帮助这本书的写作,但我们的研究生教会了我们很多关于系统的知识。我们在威斯康星大学时经常与他们交谈,并且所有真正的工作都是他们做的——通过告诉我们他们在做什么,我们每周都能学习到新事物。下面的列表包括我们当前和以前的学生,带有星号标志的名字是在我们的指导下获得博士学位的人:Abhishek Rajimwale,Andrew Krioukov,Ao Ma,Brian Forney,Chris Dragga,Deepak Ramamurthi,Florentina Popovici *,Haryadi S. Gunawi *,James Nugent,John Bent *,Jun He,Lanyue Lu,Lakshmi Bairavasundaram * ,Laxman Visampalli,Leo Arulraj,Meenali Rungta,Muthian Sivathanu *,Nathan Burnett *,Nitin Agrawal *,Ram Alagappan,Sriram Subramanian *,Stephen Todd Jones *,Suli Yang,Swaminathan Sundararaman*,Swetha Krishnan,Thanh Do*, Thanumalayan S. Pillai,Timothy Denehy*,Tyler Harter,Venkat Venkataramani,Vijay Chidambaram,Vijayan Prabhakaran *,Yiyi Zhang *,Yupu Zhang *,Zev Weiss。

最后要感谢Aaron Brown,他多年前(2009年春季)首次参加该课程,接着参加了xv6实验课程(2009年秋季),最后还成为了两个课程的研究生助教(从2010年秋季到2012年春季)。他不知疲倦的工作极大地改善了项目的状态(特别是xv6项目),因此有助于改善威斯康星大学无数本科生和研究生的学习体验。正如Aaron所说的(以他通常的简洁方式):“谢谢。”

叶芝有一句名言:“教育不是注满一桶水,而是点燃一把火。”他说得既对也错[3]。你必须“给桶注一点水”,这本书当然可以帮助你完成这部分的教育。毕竟,当你去Google面试时,他们会问你一个关于如何使用信号量的技巧问题,确切地知道信号量是什么感觉真好,对吧?

但是,叶芝的主要观点显而易见:教育的真正要点是让你对某些事情感兴趣,可以独立学习更多关于这个主题的东西,而不仅仅是你需要消化什么才能在某些课程上取得好成绩。正如我们的父亲(雷姆兹的父亲Vedat Arpaci)曾经说过的,“在课堂以外学习。”

我们编写本书以激发你对操作系统的兴趣,让你能自行阅读有关该主题的更多信息,进而与你的教授讨论该领域正在进行的所有令人兴奋的研究,甚至参与这些研究。这是一个伟大的领域,充满了激动人心和精妙的想法,以深刻而重要的方式塑造了计算历史。虽然我们知道这种火不会为你们所有人点燃,但我们希望这能对许多人,甚至是少数人有所帮助。因为一旦火被点燃,那你就真正有能力做出伟大的事情。因此,教育过程的真正意义在于:前进,学习许多新的和引人入胜的主题,通过学习不断成熟,最重要的是,找到能为你点火的东西。[4]

威斯康星大学计算机科学教授  雷姆兹和安德莉亚夫妇

[CK+08]“The xv6 Operating System”Russ Cox, Frans Kaashoek, Robert Morris, Nickolai Zeldovich.

xv6是作为原来UNIX版本6的移植版开发的,它代表了通过一种美观、干净、简单的方式来理解现代操作系统。

[F96]“Six Easy Pieces: Essentials Of Physics Explained By Its Most Brilliant Teacher”Richard P. Feynman

Basic Books, 1996

这本书摘取了1993年的《费曼物理学讲义》中6个最简单的章节。如果你喜欢物理学,那么就读一读这本很优秀的读物吧。

[HP90]“Computer Architecture a Quantitative Approach”(1st ed.) David A. Patterson and John L. Hennessy

Morgan-Kaufman, 1990

在读本科时,这本书成为了我们去攻读研究生的动力。我们后来都很高兴与Patterson合作,他为我们研究事业基础的奠定给予了极大的帮助。

[KR88]“The C Programming Language”Brian Kernighan and Dennis Ritchie Prentice-Hall, April 1988

每个人都应该拥有一本由发明该语言的人编写的C编程参考书。

[K62]“The Structure of Scientific Revolutions”Thomas S. Kuhn

University of Chicago Press, 1962

这是关于科学过程基础知识的著名读物,包括科学过程的整理工作、异常、危机和变革。我们要做的是整理工作。

[1] 这里的题外话:我们在这里所说的“免费”并不意味着开源,也不意味着该书没有受到通常保护的版权——它是受到保护的!我们的意思是你可以下载章节,并使用它们来了解操作系统。为什么不是一本开源的书,不像Linux一样是一个开源内核?当你阅读它时,这本书应该像一次对话,某人向你解释某事。因此,这就是我们的方法。

[2] 图灵奖是计算机科学的最高奖项。它就像诺贝尔奖,但你可能从未听说过。

[3] 如果他真的说了这句话。与许多名言一样,这句名言的历史也是模糊不清的。

[4] 如果这听起来像我们承认过去曾是纵火犯,那你可能理解错了。如果这听起来很俗气,好吧,因为它确实是的,但你必须原谅我们。


本书由异步社区出品,社区(https://www.epubit.com/)为你提供相关资源和后续服务。

本书为教师提供如下教学辅助资源:

如果您是教师,希望获得教学配套资源,请发邮件到contact@epubit.com.cn申请,或者在社区本书页面中直接联系本书的责任编辑。

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,单击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/selfpublish/submission即可)。

如果您是学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。

“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。

异步社区

微信服务号


教授:欢迎阅读这本书,本书英文书名为《Operating Systems:Three Easy Pieces》,由我来讲授关于操作系统的知识。请做一下自我介绍。

学生:教授,您好,我是学生,您可能已经猜到了,我已经准备好开始学习了!

教授:很好。有问题吗?

学生:有!本书为什么讲“3个简单部分”?

教授:这很简单。理查德·费曼有几本关于物理学的讲义,非常不错……

学生:啊,是《别闹了,费曼先生》的作者吗?那本书很棒!这书也会像那本书一样搞笑吗?

教授:呃……不。那本书的确很棒,很高兴你读过它。我希望这本书更像他关于物理学的讲义。将一些基本内容汇集成一本书,名为《Six Easy Pieces》。他讲的是物理学,而我们将探讨的主题是操作系统的 3 个简单部分。这很合适,因为操作系统的难度差不多是物理学的一半。

学生:懂了,我喜欢物理学。是哪3个部分呢?

教授:虚拟化(virtualization)、并发(concurrency)和持久性(persistence)。这是我们要学习的 3 个关键概念。通过学习这 3 个概念,我们将理解操作系统是如何工作的,包括它如何决定接下来哪个程序使用CPU,如何在虚拟内存系统中处理内存使用过载,虚拟机监控器如何工作,如何管理磁盘上的数据,还会讲一点如何构建在部分节点失败时仍能正常工作的分布式系统。

学生:对于您说的这些,我都没有概念。

教授:好极了,这说明你来对了地方。

学生:我还有一个问题:学习这些内容最好的方法是什么?

教授:好问题!当然,每个人都有适合自己的学习方法,但我的方法是:首先听课,听老师讲解并做好笔记,然后每个周末阅读笔记,以便更好地理解这些概念。过一段时间(比如考试前),再阅读一遍笔记来进一步巩固知识。当然老师也肯定会布置作业和项目,你需要认真完成。特别是做项目,你会编写真正的代码来解决真正的问题,这是将笔记中的概念活学活用。就像孔子说的那样……

学生:我知道!“不闻不若闻之,闻之不若见之,见之不若知之,知之不若行之。”

教授:(惊讶)你怎么知道我要说这个?

学生:这样似乎很连贯。我是孔子的粉丝,更是荀子的粉丝,实际上荀子才是说这句话的人[1]

教授:(愕然)我猜我们会相处得很愉快。

学生:教授,我还有一个问题,我们这样的对话有什么用的。我是说如果这仅是一本书,为什么您不直接上来就讲述知识呢?

教授:好问题!我觉得有的时候将自己从叙述中抽离出来,然后进行一些思考会更有用。这些对话就是思考。我们将协作探究所有这些复杂的概念。你是为此而来的吗?

学生:所以我们必须思考?好的,我正是为此而来。不过我还有什么要做的吗?看起来我好像就是为此书而生。

教授:我也是。我们开始学习吧!

[1] 儒家思想家荀子曾说过:“不闻不若闻之,闻之不若见之,见之不若知之,知之不若行之。”后来,不知怎么这句名言归到了孔子头上。感谢Jiao Dong(Rutgers)告诉我们。


如果你正在读本科操作系统课程,那么应该已经初步了解了计算机程序运行时做的事情。如果不了解,这本书(和相应的课程)对你来说会很困难:你应该停止阅读本书,或跑到最近的书店,在继续读本书之前快速学习必要的背景知识(包括Patt / Patel [PP03 ],特别是Bryant / O’Hallaron的书[BOH10],都是相当不错的)。

程序运行时会发生什么?

一个正在运行的程序会做一件非常简单的事情:执行指令。处理器从内存中获取(fetch)一条指令,对其进行解码(decode)(弄清楚这是哪条指令),然后执行(execute)它(做它应该做的事情,如两个数相加、访问内存、检查条件、跳转到函数等)。完成这条指令后,处理器继续执行下一条指令,依此类推,直到程序最终完成[1]

这样,我们就描述了冯·诺依曼(Von Neumann)计算模型[2]的基本概念。听起来很简单,对吧?但在这门课中,我们将了解到在一个程序运行的同时,还有很多其他疯狂的事情也在同步进行——主要是为了让系统易于使用。

实际上,有一类软件负责让程序运行变得容易(甚至允许你同时运行多个程序),允许程序共享内存,让程序能够与设备交互,以及其他类似的有趣的工作。这些软件称为操作系统(Operating System,OS)[3],因为它们负责确保系统既易于使用又正确高效地运行。

关键问题:如何将资源虚拟化 

我们将在本书中回答一个核心问题:操作系统如何将资源虚拟化?这是关键问题。为什么操作系统这样做?这不是主要问题,因为答案应该很明显:它让系统更易于使用。因此,我们关注如何虚拟化:操作系统通过哪些机制和策略来实现虚拟化?操作系统如何有效地实现虚拟化?需要哪些硬件支持?

我们将用这种灰色文本框来突出“关键(crux)问题”,以此引出我们在构建操作系统时试图解决的具体问题。因此,在关于特定主题的说明中,你可能会发现一个或多个关键点(是的,cruces是正确的复数形式),它突出了问题。当然,该章详细地提供了解决方案,或至少是解决方案的基本参数。

要做到这一点,操作系统主要利用一种通用的技术,我们称之为虚拟化(virtualization)。也就是说,操作系统将物理(physical)资源(如处理器、内存或磁盘)转换为更通用、更强大且更易于使用的虚拟形式。因此,我们有时将操作系统称为虚拟机(virtual machine)。

当然,为了让用户可以告诉操作系统做什么,从而利用虚拟机的功能(如运行程序、分配内存或访问文件),操作系统还提供了一些接口(API),供你调用。实际上,典型的操作系统会提供几百个系统调用(system call),让应用程序调用。由于操作系统提供这些调用来运行程序、访问内存和设备,并进行其他相关操作,我们有时也会说操作系统为应用程序提供了一个标准库(standard library)。

最后,因为虚拟化让许多程序运行(从而共享CPU),让许多程序可以同时访问自己的指令和数据(从而共享内存),让许多程序访问设备(从而共享磁盘等),所以操作系统有时被称为资源管理器(resource manager)。每个CPU、内存和磁盘都是系统的资源(resource),因此操作系统扮演的主要角色就是管理(manage)这些资源,以做到高效或公平,或者实际上考虑其他许多可能的目标。为了更好地理解操作系统的角色,我们来看一些例子。

图2.1展示了我们的第一个程序。实际上,它没有太大的作用,它所做的只是调用Spin()函数,该函数会反复检查时间并在运行一秒后返回。然后,它会打印出用户在命令行中传入的字符串,并一直重复这样做。

1    #include <stdio.h>
2    #include <stdlib.h>
3    #include <sys/time.h>
4    #include <assert.h>
5    #include "common.h"
6
7    int
8    main(int argc, char *argv[])
9    {
10       if (argc != 2) {
11           fprintf(stderr, "usage: cpu <string>\n");
12           exit(1);
13       }
14       char *str = argv[1];
15       while (1) {
16           Spin(1);
17           printf("%s\n", str);
18       }
19       return 0;
20    }

图2.1 简单示例:循环打印的代码(cpu.c)

假设我们将这个文件保存为cpu.c,并决定在一个单处理器(或有时称为CPU)的系统上编译和运行它。以下是我们将看到的内容:

prompt> gcc -o cpu cpu.c -Wall
prompt> ./cpu "A"
A
A
A
A
ˆC
prompt>

运行不太有趣:系统开始运行程序时,该程序会重复检查时间,直到一秒钟过去。一秒钟过去后,代码打印用户传入的字符串(在本例中为字母“A”)并继续。注意:该程序将永远运行,只有按下“Control-c”(这在基于UNIX的系统上将终止在前台运行的程序),才能停止运行该程序。

现在,让我们做同样的事情,但这一次,让我们运行同一个程序的许多不同实例。图2.2展示了这个稍复杂的例子的结果。

prompt> ./cpu A & ; ./cpu B & ; ./cpu C & ; ./cpu D &
[1] 7353
[2] 7354
[3] 7355
[4] 7356
A
B
D
C
A
B
D
C
A
C
B
D
...

图2.2 同时运行许多程序

好吧,现在事情开始变得有趣了。尽管我们只有一个处理器,但这 4 个程序似乎在同时运行!这种魔法是如何发生的?[4]

事实证明,在硬件的一些帮助下,操作系统负责提供这种假象(illusion),即系统拥有非常多的虚拟CPU的假象。将单个CPU(或其中一小部分)转换为看似无限数量的CPU,从而让许多程序看似同时运行,这就是所谓的虚拟化CPU(virtualizing the CPU),这是本书第一大部分的关注点。

当然,要运行程序并停止它们,或告诉操作系统运行哪些程序,需要有一些接口(API),你可以利用它们将需求传达给操作系统。我们将在本书中讨论这些API。事实上,它们是大多数用户与操作系统交互的主要方式。

你可能还会注意到,一次运行多个程序的能力会引发各种新问题。例如,如果两个程序想要在特定时间运行,应该运行哪个?这个问题由操作系统的策略(policy)来回答。在操作系统的许多不同的地方采用了一些策略,来回答这类问题,所以我们将在学习操作系统实现的基本机制(mechanism)(例如一次运行多个程序的能力)时研究这些策略。因此,操作系统承担了资源管理器(resource manager)的角色。

现在让我们考虑一下内存。现代机器提供的物理内存(physical memory)模型非常简单。内存就是一个字节数组。要读取(read)内存,必须指定一个地址(address),才能访问存储在那里的数据。要写入(write)或更新(update)内存,还必须指定要写入给定地址的数据。

程序运行时,一直要访问内存。程序将所有数据结构保存在内存中,并通过各种指令来访问它们,例如加载和保存,或利用其他明确的指令,在工作时访问内存。不要忘记,程序的每个指令都在内存中,因此每次读取指令都会访问内存。

让我们来看一个程序,它通过调用malloc()来分配一些内存(见图2.3)。

1    #include <unistd.h>
2    #include <stdio.h>
3    #include <stdlib.h>
4    #include "common.h"
5
6    int
7    main(int argc, char *argv[])
8    {
9        int *p = malloc(sizeof(int));              // a1
10       assert(p != NULL);
11       printf("(%d) memory address of p: %08x\n",
12              getpid(), (unsigned) p);            // a2
13       *p = 0;                                    // a3
14       while (1) {
15           Spin(1);
16           *p = *p + 1;
17            printf("(%d) p: %d\n", getpid(), *p); // a4
18       }
19       return 0;
20   }

图2.3 一个访问内存的程序(mem.c)

该程序的输出如下:

prompt> ./mem
(2134) memory address of p: 00200000
(2134) p: 1
(2134) p: 2
(2134) p: 3
(2134) p: 4
(2134) p: 5
ˆC

该程序做了几件事。首先,它分配了一些内存(a1行)。然后,打印出内存(a2)的地址,然后将数字0放入新分配的内存(a3)的第一个空位中。最后,程序循环,延迟一秒钟并递增p中保存的地址值。在每个打印语句中,它还会打印出所谓的正在运行程序的进程标识符(PID)。该PID对每个运行进程是唯一的。

同样,第一次的结果不太有趣。新分配的内存地址为00200000。程序运行时,它慢慢地更新值并打印出结果。

现在,我们再次运行同一个程序的多个实例,看看会发生什么(见图2.4)。我们从示例中看到,每个正在运行的程序都在相同的地址(00200000)处分配了内存,但每个似乎都独立更新了00200000处的值!就好像每个正在运行的程序都有自己的私有内存,而不是与其他正在运行的程序共享相同的物理内存[5]

prompt> ./mem &; ./mem & 
[1] 24113
[2] 24114
(24113) memory address of p: 00200000
(24114) memory address of p: 00200000
(24113) p: 1
(24114) p: 1
(24114) p: 2
(24113) p: 2
(24113) p: 3
(24114) p: 3
(24113) p: 4
(24114) p: 4
...

图2.4 多次运行内存程序

实际上,这正是操作系统虚拟化内存(virtualizing memory)时发生的情况。每个进程访问自己的私有虚拟地址空间(virtual address space)(有时称为地址空间,address space),操作系统以某种方式映射到机器的物理内存上。一个正在运行的程序中的内存引用不会影响其他进程(或操作系统本身)的地址空间。对于正在运行的程序,它完全拥有自己的物理内存。但实际情况是,物理内存是由操作系统管理的共享资源。所有这些是如何完成的,也是本书第1部分的主题,属于虚拟化(virtualization)的主题。

本书的另一个主题是并发(concurrency)。我们使用这个术语来指代一系列问题,这些问题在同时(并发地)处理很多事情时出现且必须解决。并发问题首先出现在操作系统本身中。如你所见,在上面关于虚拟化的例子中,操作系统同时处理很多事情,首先运行一个进程,然后再运行一个进程,等等。事实证明,这样做会导致一些深刻而有趣的问题。

遗憾的是,并发问题不再局限于操作系统本身。事实上,现代多线程(multi-threaded)程序也存在相同的问题。我们来看一个多线程程序的例子(见图2.5)。

1    #include <stdio.h>
2    #include <stdlib.h>
3    #include "common.h"
4
5    volatile int counter = 0;
6    int loops;
7
8    void *worker(void *arg) {
9        int i;
10       for (i = 0; i < loops; i++) {
11           counter++;
12       }
13       return NULL;
14   }
15
16   int
17   main(int argc, char *argv[])
18   { 
19       if (argc != 2) {
20           fprintf(stderr, "usage: threads <value>\n");
21           exit(1);
22       }
23       loops = atoi(argv[1]);
24       pthread_t p1, p2;
25       printf("Initial value : %d\n", counter);
26
27       Pthread_create(&p1, NULL, worker, NULL);
28       Pthread_create(&p2, NULL, worker, NULL);
29       Pthread_join(p1, NULL);
30       Pthread_join(p2, NULL);
31       printf("Final value    : %d\n", counter);
32       return 0;
33   }

图2.5 一个多线程程序(threads.c)

尽管目前你可能完全不理解这个例子(在后面关于并发的部分中,我们将学习更多的内容),但基本思想很简单。主程序利用Pthread_create()创建了两个线程(thread)[6]。你可以将线程看作与其他函数在同一内存空间中运行的函数,并且每次都有多个线程处于活动状态。在这个例子中,每个线程开始在一个名为worker()的函数中运行,在该函数中,它只是递增一个计数器,循环loops次。

下面是运行这个程序、将变量loops的输入值设置为1000时的输出结果。loops的值决定了两个worker各自在循环中增加共享计数器的次数。如果loops的值设置为1000并运行程序,你认为计数器的最终值是多少?

关键问题:如何构建正确的并发程序 

如果同一个内存空间中有很多并发执行的线程,如何构建一个正确工作的程序?操作系统需要什么原语?硬件应该提供哪些机制?我们如何利用它们来解决并发问题?

prompt> gcc -o thread threads.c -Wall -pthreadprompt> ./thread 1000Initial value : 0Final value : 2000

你可能会猜到,两个线程完成时,计数器的最终值为2000,因为每个线程将计数增加1000次。也就是说,当loops的输入值设为N时,我们预计程序的最终输出为2N。但事实证明,事情并不是那么简单。让我们运行相同的程序,但loops的值更高,然后看看会发生什么:

prompt> ./thread 100000 
Initial value : 0
Final value   : 143012     // huh?? 
prompt> ./thread 100000
Initial value : 0
Final value  : 137298    // what the??

在这次运行中,当我们提供100000的输入值时,得到的最终值不是200000,我们得到的是143012。然后,当我们再次运行该程序时,不仅再次得到了错误的值,还与上次的值不同。事实上,如果你一遍又一遍地使用较高的loops值运行程序,可能会发现有时甚至可以得到正确的答案!那么为什么会这样?

事实证明,这些奇怪的、不寻常的结果与指令如何执行有关,指令每次执行一条。遗憾的是,上面的程序中的关键部分是增加共享计数器的地方,它需要 3 条指令:一条将计数器的值从内存加载到寄存器,一条将其递增,另一条将其保存回内存。因为这 3 条指令并不是以原子方式(atomically)执行(所有的指令一次性执行)的,所以奇怪的事情可能会发生。关于这种并发(concurrency)问题,我们将在本书的第2部分中详细讨论。

本课程的第三个主题是持久性(persistence)。在系统内存中,数据容易丢失,因为像DRAM这样的设备以易失(volatile)的方式存储数值。如果断电或系统崩溃,那么内存中的所有数据都会丢失。因此,我们需要硬件和软件来持久地(persistently)存储数据。这样的存储对于所有系统都很重要,因为用户非常关心他们的数据。

硬件以某种输入/输出(Input/Output,I/O)设备的形式出现。在现代系统中,硬盘驱动器(hard drive)是存储长期保存的信息的通用存储库,尽管固态硬盘(Solid-State Drive,SSD)正在这个领域取得领先地位。

操作系统中管理磁盘的软件通常称为文件系统(file system)。因此它负责以可靠和高效的方式,将用户创建的任何文件(file)存储在系统的磁盘上。

不像操作系统为CPU和内存提供的抽象,操作系统不会为每个应用程序创建专用的虚拟磁盘。相反,它假设用户经常需要共享(share)文件中的信息。例如,在编写C程序时,你可能首先使用编辑器(例如Emacs[7])来创建和编辑C文件(emacs -nw main.c)。之后,你可以使用编译器将源代码转换为可执行文件(例如,gcc -o main main.c)。再之后,你可以运行新的可执行文件(例如./main)。因此,你可以看到文件如何在不同的进程之间共享。首先,Emacs创建一个文件,作为编译器的输入。编译器使用该输入文件创建一个新的可执行文件(可选一门编译器课程来了解细节)。最后,运行新的可执行文件。这样一个新的程序就诞生了!

为了更好地理解这一点,我们来看一些代码。图2.6展示了一些代码,创建包含字符串“hello world”的文件(/tmp/file)。

1    #include <stdio.h>
2    #include <unistd.h>
3    #include <assert.h>
4    #include <fcntl.h>
5    #include <sys/types.h>
6
7    int
8    main(int argc, char *argv[])
9    {
10       int fd = open("/tmp/file", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
11       assert(fd > -1);
12       int rc = write(fd, "hello world\n", 13);
13       assert(rc == 13);
14       close(fd);
15       return 0;
16   }

图2.6 一个进行I/O的程序(io.c)

关键问题:如何持久地存储数据 

文件系统是操作系统的一部分,负责管理持久的数据。持久性需要哪些技术才能正确地实现?需要哪些机制和策略才能高性能地实现?面对硬件和软件故障,可靠性如何实现?

为了完成这个任务,该程序向操作系统发出3个调用。第一个是对open()的调用,它打开文件并创建它。第二个是write(),将一些数据写入文件。第三个是close(),只是简单地关闭文件,从而表明程序不会再向它写入更多的数据。这些系统调用(system call)被转到称为文件系统(file system)的操作系统部分,然后该系统处理这些请求,并向用户返回某种错误代码。

你可能想知道操作系统为了实际写入磁盘而做了什么。我们会告诉你,但你必须答应先闭上眼睛。这是不愉快的。文件系统必须做很多工作:首先确定新数据将驻留在磁盘上的哪个位置,然后在文件系统所维护的各种结构中对其进行记录。这样做需要向底层存储设备发出I/O请求,以读取现有结构或更新(写入)它们。所有写过设备驱动程序[8](device driver)的人都知道,让设备代表你执行某项操作是一个复杂而详细的过程。它需要深入了解低级别设备接口及其确切的语义。幸运的是,操作系统提供了一种通过系统调用来访问设备的标准和简单的方法。因此,OS有时被视为标准库(standard library)。

当然,关于如何访问设备、文件系统如何在所述设备上持久地管理数据,还有更多细节。出于性能方面的原因,大多数文件系统首先会延迟这些写操作一段时间,希望将其批量分组为较大的组。为了处理写入期间系统崩溃的问题,大多数文件系统都包含某种复杂的写入协议,如日志(journaling)或写时复制(copy-on-write),仔细排序写入磁盘的操作,以确保如果在写入序列期间发生故障,系统可以在之后恢复到合理的状态。为了使不同的通用操作更高效,文件系统采用了许多不同的数据结构和访问方法,从简单的列表到复杂的B树。如果所有这些都不太明白,那很好!在本书的第3部分关于持久性(persistence)的讨论中,我们将详细讨论所有这些内容,在其中讨论设备和I/O,然后详细讨论磁盘、RAID和文件系统。

现在你已经了解了操作系统实际上做了什么:它取得CPU、内存或磁盘等物理资源(resources),并对它们进行虚拟化(virtualize)。它处理与并发(concurrency)有关的麻烦且棘手的问题。它持久地(persistently)存储文件,从而使它们长期安全。鉴于我们希望建立这样一个系统,所以要有一些目标,以帮助我们集中设计和实现,并在必要时进行折中。找到合适的折中是建立系统的关键。

一个最基本的目标,是建立一些抽象(abstraction),让系统方便和易于使用。抽象对我们在计算机科学中做的每件事都很有帮助。抽象使得编写一个大型程序成为可能,将其划分为小而且容易理解的部分,用C[9] 这样的高级语言编写这样的程序不用考虑汇编,用汇编写代码不用考虑逻辑门,用逻辑门来构建处理器不用太多考虑晶体管。抽象是如此重要,有时我们会忘记它的重要性,但在这里我们不会忘记。因此,在每一部分中,我们将讨论随着时间的推移而发展的一些主要抽象,为你提供一种思考操作系统部分的方法。

设计和实现操作系统的一个目标,是提供高性能(performance)。换言之,我们的目标是最小化操作系统的开销(minimize the overhead)。虚拟化和让系统易于使用是非常值得的,但不会不计成本。因此,我们必须努力提供虚拟化和其他操作系统功能,同时没有过多的开销。这些开销会以多种形式出现:额外时间(更多指令)和额外空间(内存或磁盘上)。如果有可能,我们会寻求解决方案,尽量减少一种或两种。但是,完美并非总是可以实现的,我们会注意到这一点,并且(在适当的情况下)容忍它。

另一个目标是在应用程序之间以及在OS和应用程序之间提供保护(protection)。因为我们希望让许多程序同时运行,所以要确保一个程序的恶意或偶然的不良行为不会损害其他程序。我们当然不希望应用程序能够损害操作系统本身(因为这会影响系统上运行的所有程序)。保护是操作系统基本原理之一的核心,这就是隔离(isolation)。让进程彼此隔离是保护的关键,因此决定了OS必须执行的大部分任务。

操作系统也必须不间断运行。当它失效时,系统上运行的所有应用程序也会失效。由于这种依赖性,操作系统往往力求提供高度的可靠性(reliability)。随着操作系统变得越来越复杂(有时包含数百万行代码),构建一个可靠的操作系统是一个相当大的挑战:事实上,该领域的许多正在进行的研究(包括我们自己的一些工作[BS+09,SS+10]),正是专注于这个问题。

其他目标也是有道理的:在我们日益增长的绿色世界中,能源效率(energy-efficiency)非常重要;安全性(security)(实际上是保护的扩展)对于恶意应用程序至关重要,特别是在这高度联网的时代。随着操作系统在越来越小的设备上运行,移动性(mobility)变得越来越重要。根据系统的使用方式,操作系统将有不同的目标,因此可能至少以稍微不同的方式实现。但是,我们会看到,我们将要介绍的关于如何构建操作系统的许多原则,这在各种不同的设备上都很有用。

在结束本章之前,让我们简单介绍一下操作系统的开发历史。就像任何由人类构建的系统一样,随着时间的推移,操作系统中积累了一些好想法,工程师们在设计中学到了重要的东西。在这里,我们简单介绍一下操作系统的几个发展阶段。更丰富的阐述,请参阅Brinch Hansen关于操作系统历史的佳作[BH00]。

一开始,操作系统并没有做太多事情。基本上,它只是一组常用函数库。例如,不是让系统中的每个程序员都编写低级I/O处理代码,而是让“OS”提供这样的API,这样开发人员的工作更加轻松。

通常,在这些老的大型机系统上,一次运行一个程序,由操作员来控制。这个操作员完成了你认为现代操作系统会做的许多事情(例如,决定运行作业的顺序)。如果你是一个聪明的开发人员,就会对这个操作员很好,这样他们可以将你的工作移动到队列的前端。

这种计算模式被称为批(batch)处理,先把一些工作准备好,然后由操作员以“分批”的方式运行。此时,计算机并没有以交互的方式使用,因为这样做成本太高:让用户坐在计算机前使用它,大部分时间它都会闲置,所以会导致设施每小时浪费数千美元[BH00]。

在超越常用服务的简单库的发展过程中,操作系统在管理机器方面扮演着更为重要的角色。其中一个重要方面是意识到代表操作系统运行的代码是特殊的。它控制了设备,因此对待它的方式应该与对待正常应用程序代码的方式不同。为什么这样?好吧,想象一下,假设允许任何应用程序从磁盘上的任何地方读取。因为任何程序都可以读取任何文件,所以隐私的概念消失了。因此,将一个文件系统(file system)(管理你的文件)实现为一个库是没有意义的。实际上,还需要别的东西。

因此,系统调用(system call)的概念诞生了,它是Atlas计算系统[K+61,L78]率先采用的。不是将操作系统例程作为一个库来提供(你只需创建一个过程调用(procedure call)来访问它们),这里的想法是添加一些特殊的硬件指令和硬件状态,让向操作系统过渡变为更正式的、受控的过程。

系统调用和过程调用之间的关键区别在于,系统调用将控制转移(跳转)到OS中,同时提高硬件特权级别(hardware privilege level)。用户应用程序以所谓的用户模式(user mode)运行,这意味着硬件限制了应用程序的功能。例如,以用户模式运行的应用程序通常不能发起对磁盘的I/O请求,不能访问任何物理内存页或在网络上发送数据包。在发起系统调用时 [通常通过一个称为陷阱(trap)的特殊硬件指令],硬件将控制转移到预先指定的陷阱处理程序(trap handler)(即预先设置的操作系统),并同时将特权级别提升到内核模式(kernel mode)。在内核模式下,操作系统可以完全访问系统的硬件,因此可以执行诸如发起I/O请求或为程序提供更多内存等功能。当操作系统完成请求的服务时,它通过特殊的陷阱返回(return-from-trap)指令将控制权交还给用户,该指令返回到用户模式,同时将控制权交还给应用程序,回到应用离开的地方。

操作系统的真正兴起在大主机计算时代之后,即小型机(minicomputer)时代。像数字设备公司(DEC)的PDP系列这样的经典机器,让计算机变得更加实惠。因此,不再是每个大型组织拥有一台主机,而是组织内的一小群人可能拥有自己的计算机。毫不奇怪,这种成本下降的主要影响之一是开发者活动的增加。更聪明的人接触到计算机,从而让计算机系统做出更有趣和漂亮的事情。

特别是,由于希望更好地利用机器资源,多道程序(multiprogramming)变得很普遍。操作系统不是一次只运行一项作业,而是将大量作业加载到内存中并在它们之间快速切换,从而提高CPU利用率。这种切换非常重要,因为I/O设备很慢。在处理I/O时让程序占着CPU,浪费了CPU时间。那么,为什么不切换到另一份工作并运行一段时间?

在I/O进行和任务中断时,要支持多道程序和重叠运行。这一愿望迫使操作系统创新,沿着多个方向进行概念发展。内存保护(memory protection)等问题变得重要。我们不希望一个程序能够访问另一个程序的内存。了解如何处理多道程序引入的并发(concurrency)问题也很关键。在中断存在的情况下,确保操作系统正常运行是一个很大的挑战。我们将在本书后面研究这些问题和相关主题。

当时主要的实际进展之一是引入了UNIX操作系统,主要归功于贝尔实验室(电话公司)的Ken Thompson和Dennis Ritchie。UNIX从不同的操作系统获得了许多好的想法(特别是来自Multics [O72],还有一些来自TENEX [B+72]和Berkeley分时系统[S+68]等系统),但让它们更简单易用。很快,这个团队就向世界各地的人们发送含有UNIX源代码的磁带,其中许多人随后参与并添加到系统中。请参阅补充了解更多细节[10]

除了小型计算机之外,还有一种新型机器,便宜,速度更快,而且适用于大众:今天我们称之为个人计算机(Personal Computer,PC)。在苹果公司早期的机器(如Apple II)和IBM PC的引领下,这种新机器很快就成为计算的主导力量,因为它们的低成本让每个桌子上都有一台机器,而不是每个工作小组共享一台小型机。

遗憾的是,对于操作系统来说,个人计算机起初代表了一次巨大的倒退,因为早期的系统忘记了(或从未知道)小型机时代的经验教训。例如,早期的操作系统,如DOS(来自微软的磁盘操作系统),并不认为内存保护很重要。因此,恶意程序(或者只是一个编程不好的应用程序)可能会在整个内存中乱写乱七八糟的东西。第一代macOS(V9及更早版本)采取合作的方式进行作业调度。因此,意外陷入无限循环的线程可能会占用整个系统,从而导致重新启动。这一代系统中遗漏的操作系统功能造成的痛苦列表很长,太长了,因此无法在此进行全面的讨论。

幸运的是,经过一段时间的苦难后,小型计算机操作系统的老功能开始进入台式机。例如,macOS X的核心是 UNIX,包括人们期望从这样一个成熟系统中获得的所有功能。Windows在计算历史中同样采用了许多伟大的思想,特别是从Windows NT开始,这是微软操作系统技术的一次巨大飞跃。即使在今天的手机上运行的操作系统(如Linux),也更像小型机在20世纪70年代运行的,而不像20世纪80年代PC运行的那种操作系统。很高兴看到在操作系统开发鼎盛时期出现的好想法已经进入现代世界。更好的是,这些想法不断发展,为用户和应用程序提供更多功能,让现代系统更加完善。

补充:UNIX的重要性 

在操作系统的历史中,UNIX的重要性举足轻重。受早期系统(特别是MIT著名的Multics系统)的影响,UNIX汇集了许多了不起的思想,创造了既简单又强大的系统。

最初的“贝尔实验室”UNIX的基础是统一的原则,即构建小而强大的程序,这些程序可以连接在一起形成更大的工作流。在你输入命令的地方,shell提供了诸如管道(pipe)之类的原语,来支持这样的元(meta-level)编程,因此很容易将程序串起来完成更大的任务。例如,要查找文本文件中包含单词“foo”的行,然后要计算存在多少行,请键入:grep foo file.txt | wc -l,从而使用grep和wc (单词计数)程序来实现你的任务。

UNIX环境对于程序员和开发人员都很友好,并为新的C编程语言提供了编译器。程序员很容易编写自己的程序并分享它们,这使得UNIX非常受欢迎。作为开放源码软件(open-source software)的早期形式,作者向所有请求的人免费提供副本,这可能帮助很大。

代码的可得性和可读性也非常重要。用C语言编写的美丽的小内核吸引其他人摆弄内核,添加新的、很酷的功能。例如,由Bill Joy领导的伯克利创业团队发布了一个非常棒的发行版(Berkeley Systems Distribution,BSD),该发行版拥有先进的虚拟内存、文件系统和网络子系统。Joy后来与朋友共同创立了Sun Microsystems。

遗憾的是,随着公司试图维护其所有权和利润,UNIX的传播速度有所放慢,这是律师参与其中的不幸(但常见的)结果。许多公司都有自己的变种:Sun Microsystems的SunOS、IBM的AIX、HP的HPUX(又名H-Pucks)以及SGI的IRIX。AT&T/贝尔实验室和这些其他厂商之间的法律纠纷给UNIX带来了阴影,许多人想知道它是否能够存活下来,尤其是Windows推出后并占领了大部分PC市场……

 

补充:然后出现了Linux 

幸运的是,对于UNIX来说,一位名叫Linus Torvalds的年轻芬兰黑客决定编写他自己的UNIX版本,该版本严重依赖最初系统背后的原则和思想,但没有借用原来的代码集,从而避免了合法性问题。他征集了世界各地许多其他人的帮助,不久,Linux就诞生了(同时也开启了现代开源软件运动)。

随着互联网时代的到来,大多数公司(如谷歌、亚马逊、Facebook和其他公司)选择运行Linux,因为它是免费的,可以随时修改以适应他们的需求。事实上,如果不存在这样一个系统,很难想象这些新公司的成功。随着智能手机成为占主导地位的面向用户的平台,出于许多相同的原因,Linux也在那里找到了用武之地(通过Android)。史蒂夫·乔布斯将他的基于UNIX的NeXTStep操作环境带到了苹果公司,从而使得UNIX在台式机上非常流行(尽管很多苹果技术用户可能都不知道这一事实)。因此,UNIX今天比以往任何时候都更加重要。如果你相信有计算之神,那么应该感谢这个美妙的结果。

至此,我们介绍了操作系统。今天的操作系统使得系统相对易于使用,而且你今天使用的几乎所有操作系统都受到本章讨论的操作系统发展的影响。

由于篇幅的限制,我们在本书中将不会涉及操作系统的一些部分。例如,操作系统中有很多网络代码。我们建议你去上网络课以便更多地学习相关知识。同样,图形设备尤为重要。请参加图形课程以扩展你在这方面的知识。最后,一些操作系统书籍谈论了很多关于安全性的内容。我们会这样做,因为操作系统必须在正在运行的程序之间提供保护,并为用户提供保护文件的能力,但我们不会深入研究安全课程中可能遇到的更深层次的安全问题。

但是,我们将讨论许多重要的主题,包括CPU和内存虚拟化的基础知识、并发以及通过设备和文件系统的持久性。别担心!虽然有很多内容要介绍,但其中大部分都很酷。这段旅程结束时,你将会对计算机系统的真实工作方式有一个全新的认识。现在开始吧!

[BS+09]“Tolerating File-System Mistakes with EnvyFS”

Lakshmi N. Bairavasundaram, Swaminathan Sundararaman, Andrea C. Arpaci-Dusseau, Remzi

H. Arpaci-Dusseau

USENIX ’09, San Diego, CA, June 2009

一篇有趣的文章,讲述同时使用多个文件系统以容忍其中任何一个文件系统出现错误。

[BH00]“The Evolution of Operating Systems”

P. Brinch Hansen

In Classic Operating Systems: From Batch Processing to Distributed Systems Springer-Verlag, New York, 2000

这篇文章介绍了与具有历史意义的系统相关的内容。

[B+72]“TENEX, A Paged Time Sharing System for the PDP-10”

Daniel G. Bobrow, Jerry D. Burchfiel, Daniel L. Murphy, Raymond S. Tomlinson CACM, Volume 15, Number 3, March 1972

TENEX拥有现代操作系统中的许多机制。请阅读更多关于它的信息,看看在20世纪70年代早期已经有了哪些创新。

[B75]“The Mythical Man-Month”Fred Brooks

Addison-Wesley, 1975

一本关于软件工程的经典教科书,非常值得一读。

[BOH10]“Computer Systems: A Programmer’s Perspective”Randal E. Bryant and David R. O’Hallaron

Addison-Wesley, 2010

关于计算机系统工作原理的另一本卓越的图书,与本书的内容有一点点重叠——所以,如果你愿意,你可以跳过本书的最后几章,或者直接阅读它们,以获取关于某些相同材料的不同观点。毕竟,健全与完善自己知识的一个好方法,就是尽可能多地听取其他观点,然后在此问题上扩展自己的观点和想法。

[K+61]“One-Level Storage System”

T. Kilburn, D.B.G. Edwards, M.J. Lanigan, F.H. Sumner IRE Transactions on Electronic Computers, April 1962

Atlas开创了你在现代系统中看到的大部分概念。但是,这篇论文并不是最好的读物。如果你只读一篇文章,可以了解一下下面的历史观点[L78]。

[L78]“The Manchester Mark I and Atlas: A Historical Perspective”

S.H. Lavington

Communications of the ACM archive Volume 21, Issue 1 (January 1978), pages 4-12

关于计算机系统早期发展的历史和Atlas的开拓性工作。当然,我们可以自己阅读Atlas的论文,但是这篇论文提供了一个对计算机系统的很好的概述,并且增加了一些历史观点。

[O72]“The Multics System: An Examination of its Structure”Elliott Organick, 1972

Multics的完美概述。这么多好的想法,但它是一个过度设计的系统,目标太多,因此从未真正按预期工作。Fred Brooks是所谓的“第二系统效应”的典型例子[B75]。

[PP03]“Introduction to Computing Systems: From Bits and Gates to C and Beyond”

Yale N. Patt and Sanjay J. Patel

McGraw-Hill, 2003

我们最喜欢的计算系统图书之一。它从晶体管开始讲解,一直讲到C。书中早期的素材特别好。

[RT74]“The UNIX Time-Sharing System”Dennis M. Ritchie and Ken Thompson

CACM, Volume 17, Number 7, July 1974, pages 365-375

关于UNIX的杰出总结,作者撰写此书时,UNIX正在计算世界里占据统治地位。

[S68]“SDS 940 Time-Sharing System”Scientific Data Systems Inc.

TECHNICAL MANUAL, SDS 90 11168 August 1968

这是我们可以找到的一本不错的技术手册。阅读这些旧的系统文件,能看到在20世纪60年代后期技术发展的进程,这很有意思。伯克利时分系统(最终成为SDS系统)背后的核心构建者之一是Butler Lampson,后来他因系统贡献而获得图灵奖。

[SS+10]“Membrane: Operating System Support for Restartable File Systems”Swaminathan Sundararaman, Sriram Subramanian, Abhishek Rajimwale, Andrea C. Arpaci-Dusseau, Remzi H. Arpaci-Dusseau, Michael M. Swift FAST ’10, San Jose, CA, February 2010

写自己的课程注解的好处是:你可以为自己的研究做广告。但是这篇论文实际上非常简洁。当文件系统遇到错误并崩溃时,Membrane会自动重新启动它,所有这些都不会导致应用程序或系统的其他部分受到影响。

[1] 当然,现代处理器在背后做了许多奇怪而可怕的事情,让程序运行得更快。例如,一次执行多条指令,甚至乱序执行并完成它们!但这不是我们在这里关心的问题。我们只关心大多数程序所假设的简单模型:指令似乎按照有序和顺序的方式逐条执行。

[2] 冯·诺依曼是计算系统的早期先驱之一。他还完成了关于博弈论和原子弹的开创性工作,并在NBA打了6年球。好吧,其中有一件事不是真的。

[3] 操作系统的另一个早期名称是监管程序(super visor),甚至叫主控程序(master control program)。显然,后者听起来有些过分热情(详情请参阅电影《Tron》),因此,谢天谢地,“操作系统”最后胜出。

[4] 请注意我们如何利用& 符号同时运行4个进程。这样做会在tcsh shell的后台运行一个作业,这意味着用户能够立即发出下一个命令,在这个例子中,是另一个运行的程序。命令之间的分号允许我们在tcsh中同时运行多个程序。如果你使用的是不同的shell(例如bash),它的工作原理会稍有不同。关于详细信息,请阅读在线文档。

[5] 要让这个例子能工作,需要确保禁用地址空间随机化。事实证明,随机化可以很好地抵御某些安全漏洞。请自行阅读更多的相关资料,特别是如果你想学习如何通过堆栈粉碎黑客对计算机系统的攻击入侵。我们不会推荐这样的东西……

[6] 实际的调用应该是小写的pthread_create()。大写版本是我们自己的包装函数,它调用pthread_create(),并确保返回代码指示调用成功。详情请参阅代码。

[7] 你应该用Emacs。如果用vi,则可能会出现问题。 如果你用的不是真正的代码编辑器,那更糟糕。

[8] 设备驱动程序是操作系统中的一些代码,它们知道如何与特定的设备打交道。我们稍后会详细讨论设备和设备驱动程序。

[9] 你们中的一些人可能不同意将C称为高级语言。不过,请记住,这是一门操作系统课程,我们很高兴不需要一直用汇编语言写程序!

[10] 我们将使用补充和其他相关文本框,让你注意到不太适合文本主线的各种内容。有时候,我们甚至会用它们来开玩笑,为什么在这个过程中没有一点乐趣?是的,许多笑话都很糟糕。


相关图书

Web应用安全
Web应用安全
ASM全埋点开发实战
ASM全埋点开发实战
龙芯嵌入式系统原理与应用开发
龙芯嵌入式系统原理与应用开发
沉浸式剖析OpenHarmony源代码:基于LTS 3.0版本
沉浸式剖析OpenHarmony源代码:基于LTS 3.0版本
统信UOS系统管理教程
统信UOS系统管理教程
深入解析Windows操作系统(卷2)  (英文版·第7版)
深入解析Windows操作系统(卷2) (英文版·第7版)

相关文章

相关课程