1. Introduction#

欢迎来到Pintos。Pintos是用于80x86体系结构的简单操作系统框架。 它支持内核线程、加载和运行用户程序以及文件系统,但是它是以非常简单的方式实现这些内容的。通过Pintos项目,您将在这三个方面增强Pintos的功能。您还将添加虚拟内存的实现。

理论上讲,Pintos可以在常规的IBM兼容PC上运行。不幸的是,向每位学生提供专用于Pintos的专用PC是不切实际的。因此,我们将在系统模拟器中运行Pintos项目,模拟器是这样一个程序,该程序可以足够精确地模拟80x86CPU及其外围设备,以便操作系统和软件不加修改就可以在其上运行。 本课程我们将采用QEMU模拟器.出于速度以及减少兼容性问题的考虑,不建议使用Bochs. Pintos也可以在VMware Player上运行。

操作系统课程设计以需要花费大量时间而闻名,这是很自然的事。我们将尽力减少工作量,例如,提供大量支持材料,但是还是需要学生完成许多艰苦的工作。我们欢迎您的反馈。如果您对我们如何减少作业中的不必要开销,只保留最重要的根本问题有任何建议,请告诉我们。

本章介绍如何开始使用Pintos。在开始任何项目的工作之前,您应该读完本章的所有内容。

1.1 Getting Started#

我们将在配置了Intel CPU的Ubuntu18.04上测试您的代码,并且此处给出的说明均在此环境下进行。请尽量采用类似环境进行开发。 (参见 G. 安装Pintos.

一旦您登录本地或远程计算机,首先添加二进制可执行文件目录到您的PATH环境变量。比如使用export命令

export PATH=/home/user/osd:$PATH

1.1.1 源码树概述#

现在,您可以将Pintos的源提取到名为pintos/src的目录中。

让我们看看里面有什么。 这是您应该在pintos/src中看到的目录结构:

threads/\ 基本内核的源代码,您将从此处开始对项目1进行修改.

userprog/\ 用户程序加载器的源代码,从项目2开始您将对其进行修改.

vm/\ 几乎是空的目录。 您将在此处实现项目3虚拟内存。

filesys/\ 基本文件系统的源代码。您将从项目2开始使用此文件系统,但是直到项目4才将其修改。

devices/\ I/O设备接口的源代码:键盘、计时器、磁盘等。您将在项目1中修改计时器实现。除此以外您无需更改此代码。

lib/\ 标准C库的子集的实现。该目录中的代码文件将被编译进Pintos内核和Pintos中运行的用户程序(从项目2开始)。在内核代码和用户程序中,都可以使用#include <...>包含在此目录中的头文件。您几乎不需要修改此代码。

lib/kernel/\ C库中仅包含在Pintos内核中的部分(即用户程序不使用)。它还包括您在编写内核代码时可以使用的某些数据类型的实现:位图、双向链表和哈希表。在内核中,此目录中的头文件可以使用#include <...>

lib/user/\ C库中仅包含在Pintos用户程序中的部分。 在用户程序中,可以使用#include <...>表示法包含此目录中的头文件。

tests/ \ 每个项目的测试文件。如果可以帮助您测试提交的内容,您可以修改此代码,但是判题器运行测试之前,会将其替换为原始代码。

examples/ \ 从项目2开始使用的示例用户程序.

misc/ \ utils/\ 如果您决定尝试在自己的计算机上使用Pintos,这些文件可能会派上用场。否则,您可以忽略它们。

1.1.2 构建 Pintos#

下一步,构建为第一个项目提供的源代码。首先, cdthreads文件夹下. 接着,发送“make” 命令. 这将在threads目录下创建一个 build 文件夹, 并生成一个Makefile和一些子目录, 然后在build目录中构建内核. 整个构建应少于30秒.

构建之后,以下是build目录中的你感兴趣的文件:

`Makefile`\ pintos/src/Makefile.build的副本. 它描述了如何构建内核. 更多信息,请参阅 Adding Source Files.

`kernel.o`\ 整个内核的目标文件。这是将从每个单独的内核源文件编译的目标文件链接到单个目标文件中的结果。它包含调试信息,因此您可以运行GDB(请参阅E.5 GDB或`backtrace`(请参见其E.4 Backtraces一节)进行调试。

`kernel.bin`\ 内核的内存映像,即加载到内存中以运行Pintos内核的确切字节。这只是去掉了调试信息的kernel.o,它节省了很多空间,从而使内核免受内核加载程序设计施加的512kB大小限制。

`loader.bin`\ 内核加载程序的内存映像,这是用汇编语言编写的一小段代码,可将内核从磁盘读取到内存中并启动它。它正好是512字节长,这个大小是由PC BIOS决定的。

`build `的子目录包含目标文件(.o)和依赖项文件(.d),均由编译器生成。依赖文件告诉make当其他源文件或头文件被更改时,哪些源文件需要重新编译。

1.1.3 运行 Pintos#

我们提供了一个在模拟器中方便运行Pintos的程序,称为“pintos”,它应该被加到您的PATH变量里。在最简单的情况下,您可以调用“pintos 参数...” 来调用pintos。每个参数都传递给Pintos内核以使其起作用。

试试看。首先cd到新创建的build目录中。然后发出命令“pintos run alarm-multiple”,这表示将参数“run alarm-multiple”传递给Pintos内核。 在这些参数中,run指示内核运行测试,alarm-multiple是运行的测试。 然后Pintos引导并运行“alarm-multiple”测试程序,该程序会输出几屏文本。这是因为Pintos将所有输出都发送到VGA显示器和第一个串行端口,并且默认情况下,串行端口连接到Qemu的stdin和stdout。您可以通过在命令行重定向来将串行输出记录到文件中,例如 pintos run alarm-multiple > logfile。

“pintos”程序为配置模拟器或虚拟硬件提供了多个选项。如果要指定任何选项,则它们必须放在传递给Pintos内核的命令之前,并由“--”隔开,以便整个命令看起来像`pintos option...-- argument... `。没有任何参数调用`pintos`,则可以查看可用选项的列表。这些选项包括可以选择要使用的模拟器:默认为Bochs,但“--qemu”选择QEMU。您可以使用调试器运行模拟器(请参见E.5 GDB部分。可以设置要给虚拟机的内存量。最后,您可以选择显示VM输出的方式:使用“-v”关闭VGA显示,“-t“将您的终端窗口用作VGA显示而不是打开新窗口(仅Bochs),或使用“ -s”禁止来自stdin的串行输入”和输出到“stdout”的输出。

Pintos内核具有run以外的命令和选项。目前这些功能不是很有趣,但是您可以使用“-h”查看它们的列表,即 pintos -h。

1.1.4 调试与测试#

在调试代码时,能够两次运行一个程序并使它执行完全相同的操作非常有用。这样,在第二次及以后的运行中,您可以进行新观察而不必丢弃或验证旧观察。此属性称为“可重现性”。Bochs模拟器可以设置成支持可重现性的。

当然,可重现性的前提是从一次运行复制到下一次运行时,每次的输入都必须相同。为了这个目的,我们必须模拟整个计算机,这意味着计算机的每个部分都必须相同。例如,您必须使用相同的命令行参数,相同的磁盘,相同的Bochs版本,并且您不得敲击键盘上的任何键(因为您无法确保每次都在相同的时间点敲击它们)

尽管可重现性对于调试很有用,但它是测试线程同步的一个问题,线程同步是大多数项目的重要组成部分。特别是,将Bochs设置为可重现时,计时器中断将在完全可重现的点出现,因此线程切换也将如此。这意味着,与只运行一次测试相比,多次运行同一测试不会给您更大的信心。

编者按:原文的意思,Bochs可以设定为可重现的,这样无论跑多少次,结果都是一样,为了测试调度,CS140的老师们故意加上了jitter功能,以破坏完全一致的时间中断的发生时间。我们的项目里采用Qemu,所以这段我就不翻译了。

QEMU模拟器可以作为Bochs的替代品使用(调用“pintos”时使用“ --qemu”)。 QEMU仿真器比Bochs快得多,但是它仅支持实时仿真,并且没有可重现的模式。

1.2 Grading#

我们将根据测试结果和设计质量对您的作业进行评分,每一项都占您成绩的50%。

1.2.1 测试#

您的测试结果得分将基于我们的测试。每个项目都有几个测试,每个测试的名称都以“tests”开头。测试提交完成后,从项目build目录中调用make check。这将构建并运行每个test,并为每个test打印“pass”或“fail”消息。如果测试失败,make check同时打印失败原因的一些详细信息。运行完所有测试后,“make check”也会打印测试结果的摘要。

对于项目1,测试可能会在Bochs中运行得更快。对于其余的项目,它们在QEMU中运行得更快。我们要求在实验中统一使用QEMU模拟器,即在make命令行中指定“SIMULATOR=--qemu”。

您也可以一次运行单个测试。给定的测试t,将其输出写入“t.output”,然后一个脚本对输出内容计分,确定为“pass”或“fail”,并将结论写入“t.result”。要运行和评分单个测试,请从build目录中明确创建.result文件,例如 make tests/threads/alarm-multiple.result。如果make显示测试结果是最新的,但是您还是想重新运行它,请运行make clean或手动删除.output文件。默认情况下,每个测试都会仅在完成时反馈,而不在运行期间反馈。如果愿意,可以通过在make命令行上指定VERBOSE=1来观察每个测试的进度,即像make check VERBOSE=1一样。您还可以使用PINTOSOPTS='...'为测试运行pintos提供任意选项,例如使用make check PINTOSOPTS='-j 1'选择一个抖动值1(请参阅[1.1.4 调试与测试]。所有测试和相关文件位于“pintos/src/tests”中。在测试您提交的内容之前,我们将使用未经修改的原始副本替换该目录的内容,以确保使用正确的测试。因此,如果可以帮助调试,您可以修改某些测试,但是我们将运行原始测试。

所有软件都有错误,因此我们的某些测试可能存在缺陷。如果您认为测试失败是测试中的错误,而不是代码中的错误,请指出。我们将研究它,并在必要时进行修复。

请不要试图利用测试包的慷慨来获利。您的代码必须在一般情况下正常工作,而不仅是针对我们提供的测试用例。例如,将内核的行为明确地基于正在运行的测试用例的名称是不可接受的。绕开测试用例的尝试将不会获得任何荣誉。如果您认为您的解决方案可能在此处处于灰色区域,请向我们询问。

1.2.2 Design#

我们将根据设计文档和您提交的源代码来判断您的设计。我们将阅读您的整个设计文档以及您的许多源代码。
不要忘记,包括设计文档在内的设计质量是项目评分的50%。花一两个小时编写一个好的设计文档,比花大量时间获取最后5%的测试分数,然后尝试在最后15分钟内匆忙编写设计文档要好。

1.2.2.1 Design Document#

我们为每个项目提供一个设计文档模板。对于项目的每个重要部分,模板在四个方面提出问题:

数据结构\ 本节的说明始终相同: 本节描述每个新建或修改的struct或struct成员、全局或静态变量、typedef或枚举的声明。用25个字以内的文字来确定每个符号的目的。

第一部分是机械的。只需将新的或修改的声明复制到设计文档中,即可为我们突出显示对数据结构的实际更改。每个声明都应在源代码中包含应随附的注释(请参见下文)。我们还要求对每个新的或更改的数据结构的目的进行非常简短的描述。限制为25个字以内,这是一个指南,旨在节省您的时间并避免与以后的区域重复。

算法\ 您可以在这里通过探究您对代码理解的问题告诉我们代码的工作方式。我们可能无法轻松地从代码中找出答案,因为针对大多数OS问题存在许多创造性的解决方案。请帮助我们。您的答案应低于作业中对要求的高级描述。我们也已经阅读了作业,因此没有必要重复或改写那里的内容。另一方面,您的答案应该高于代码本身的低水平。不要对您的代码进行逐行的总结,而是回答代码如何实现要求。

Synchronization 操作系统内核是一个复杂的多线程程序,其中很难同步多个线程。本部分询问您如何选择同步此特定类型的活动。

Rationale 其他部分主要询问“什么”和“如何”,而基本原理部分则集中于“为什么”。在这里,我们要求您通过解释为什么所做的选择比其他选择更好来证明一些设计决策的合理性。您可能可以根据时间和空间的复杂性来陈述这些,可以将其作为粗略或非正式的论点(不需要正式语言或证明)。

任何不完整、含糊或无响应的设计文档,或在没有正当理由的情况下偏离模板的文档可能会受到处罚。不正确的大小写、标点、拼写或语法也可能会扣掉一些分. 参阅D. Project Documentation, 一个虚构项目的设计文档.

1.2.2.2 Source Code#

还可以通过查看源代码来判断您的设计。我们通常会基于diff -urpb pintos.orig pintos.submitted命令的输出来查看原始Pintos源代码树与您提交的文件之间的差异。我们将尝试将您对设计的描述与提交的代码相匹配。描述和实际代码之间的重要差异以及我们通过抽查发现的任何错误都将受到处罚。

源代码设计中最重要的方面是那些与项目中所涉及的操作系统问题特别相关的方面。例如,索引节点的组织是文件系统设计的重要组成部分,因此,在文件系统项目中,设计不良的索引节点将失去分数。其他问题则不那么重要。例如,多个Pintos设计问题要求“优先队列”,即动态集合,可以从中快速提取最小(或最大)项目。快速优先级队列可以通过多种方式实现,但是我们不希望您构建精美的数据结构,即使它可能会提高性能。相反,欢迎您使用链接列表(Pintos甚至提供了一个具有排序和查找最小最大值的函数)。

Pintos以一致的风格编写。在现有Pintos源文件中进行添加和修改,而不是相反。在新的源文件中,优先采用现有的Pintos样式,但至少要使您的代码自洽。代码不应由各种不同样式拼凑而成。使用水平和垂直空白区域使代码可读。在每个结构、结构成员、全局或静态变量、typedef、枚举和函数定义上添加简短注释。修改代码时更新现有注释。不要注释掉或使用预处理器忽略代码块(而应将其完全删除)。使用断言来记录关键不变式。 为了清楚起见,将代码分解为函数。由于违反了这些或其他“常识”软件工程实践而难以理解的代码将受到处罚。

最后,请记住您的听众。编写代码主要是供人类阅读。它也必须为编译器所接受,但是编译器并不关心它的外观或编写的好坏。

Pintos是根据允许免费使用、修改和分发的自由许可进行分发的。学生和使用Pintos的其他人拥有他们编写的代码,并且可以将其用于任何目的。Pintos不提供任何担保,即使是针对特定目的的适销性或适用性也不担保。 有关许可证的详细信息和免责条款,请参见\ [License ]()部分。 在斯坦福大学CS 140课程中,请避免阅读任何在线或其他地方提供的家庭作业解决方案,以尊重荣誉精神和荣誉守则。 允许读取其他操作系统内核(例如Linux或FreeBSD)的源代码,但不要从字面上复制代码。 请在设计文档中引用启发您自己的代码。