介绍一下CMU的操作系统(OS)这门课

avatar 98269
albusshin
17151
5
这是我写的课程总结系列当中的一篇,博客文章发在了这里blog.albusshin.com
在地里也发一份让大家简单了解一下这门课。

## 写在前面
觉得自己很幸运,能够在上学期间选得上CMU的OS。这篇文章主要回顾一下自己上学期在OS课上所学的内容,并且给学弟学妹们一些建议和参考,这门享有盛名的课值不值得选,适不适合你,等等。对于决定了选这门课的同学们也会写一下各个地方的注意事项等等。

## 课程内容
惯例先说评分机制。不吹不黑,这门课要拿A还是稍有难度的;如果非要拿A的话,选了这门课的这个学期肯定要牺牲一些其他东西,可能要在`[高质量的OS, 高质量的其他课, social, 高质量的睡眠]`当中四选二。只说普通人,大神另当别论。

参与评分的项目有五个Project,两个家庭作业,一个期中考试,一个期末考试,一份读书报告。这门课之所以奇葩,就是因为评分的地方特别多,而且每个地方都要做得很好。就拿期中考试和期末考试举个例子,如果有一次考烂了,基本上就告别A了。如果考烂了还想要拿A的话,kernel project要做得特别出彩才行,然而考试考得好比kernel做得出彩要简单得多。下面就一一叙述下这门课包含的内容。

### Lectures
这门课的教授长时间是[Dave](cs.cmu.edu),讲课方式有一种单口相声的水准,但是课下和同学们的交流方式褒贬不一。我自己没被他嘲讽过,但是我亲眼见到他嘲讽过别人,语气咄咄逼人……但是其实大部分时候还是很不错的。另外一个教授是[Todd](cs.cmu.edu),Deadlock, Parallellism, File system是他来讲,是一些理论比较复杂的东西。两个人各有特色,Dave更加偏相声,比较口语化,不太容易溜号,Todd更清晰些但是我听他的课却比较容易走神。

课程一般是每节课40张PPT左右,一节课50分钟,所以平均下来基本上一分钟一张多点。Dave的话一般还会抽出些时间讲讲段子,所以每张PPT时间就更少了。但是这并不代表东西少,其实每一个topic都很重要,尤其是课堂上用了超过3张PPT详细讨论过的话题,不论是考试还是project当中都会用得到。比如讲Synchronization的时候,在mutex的实现那里讲过很多错误的实现方法,这些就需要全部记下来并且在P2当中想出一个你自己认为的更好的解法。上课讲到的内容基本上是根据他推荐的教程两本当中的一本:Operating Systems: Principles and Practice来设计的,所以如果实在是错过了一节课并且没有录音的话,可以看几遍这本书当中的相应章节。但是还是强烈推荐去上课,或者至少听同学带回来的课堂录音,这样可以确保没有落下东西,因为在OS这门课当中,落下一节课可能真的就要被一直落下了。甚至有时候可能老师在讲一两张PPT的时候你走神了,那接下来的30张就都可以放弃治疗了。


### Projects
OS的项目是这门课的卖点。本门课一共5个项目,分别是P0, P1, P2, P3, P4。难度从P0到P3依次递增,P4可能会降低点(误)。Project的难点在于找到corner cases。你要literally找到所有的corner cases并且把它们妥善的处理掉。在写应用层级的代码的时候,可以把异常处理交给语言框架或者操作系统,只需要`throw`即可。然而在OS这一层,下面是固件和硬件,锅从应用级代码接过来之后就不能再甩了,如果有地方没处理好的话锅就是操作系统的。所以一定要把*所有的*corner cases处理好。

一共3个grace day,可以用在任何一个项目上,但是每个项目最多用两个。我个人强烈建议把grace day留到P3。别看P3有两个月的时间,单单补文档/重构代码就能花掉两天,而且还是两个人一起搞。另:重构有风险,git要用好。

从P0到P2,教授和助教们会把你写的所有的代码打印出来,装订成本,并且进行逐行批改。如果助教在你的代码当中发现了一些比较严重的问题,会用朱笔写上"See course staff",邀请你去和他们进行详细讨论。代码中其他的问题,也会写在你的代码旁边。评分的话,对每一个Project,教授都会事先公开一些test cases,然后自己再私藏一些test cases。在评分的时候由自动化测试结果和手工批改结果共同生成项目成绩。

#### P0
P0是一个课程小热身,让你有机会试试水,给你一周的时间体验一下OS这门课最简单的项目是个什么样子(还来得及退课选其他的)。P0的内容是用C和汇编语言写一个stack crawler,爬栈并且输出调用函数的stacktrace。这是一个用户级的程序,还没有到kernel级。所以难度的话可能比15-213的malloc lab要低一些。但是还要说一句:corner cases。OS这门课对corner case的严肃性,我第一次体验到是在P0的feedback。要求写的东西是一个代码库,这也就意味着你写的代码可能会被很多其他人调用,所以处理corner case也就尤为重要。其中有一个corner case我没有处理好,但也是让我大开眼界(这种test case都有?!?!),让我在之后的项目当中更加注意corner cases,但具体那个test case是什么在这里由于课程保密性就不说了。

#### P1
P1写键盘驱动,显示和控制台驱动,时钟驱动,并且用你所写的驱动写一个这学期所指定的游戏,时间十一天。这是这节课第一次接触kernel层级的代码,用一个叫做Simics的模拟软件来进行电脑硬件模拟。

内容听起来好像挺多挺恐怖的,然而仔细去做的话还是可以的。值得一提的是,我在P1用掉了一个grace day,原因是最后23:30的时候我发现自己的代码源文件目录结构和提交网页上的不一样,于是自己重新收拾了一下,结果make出错。只能用了一天grace。还有值得一提的就是,P1那天上午是OS课,我和partner上网P2/P3的partner我俩下课之后匆忙赶回INI写代码文档,结果写到了晚上十一点,我还用了grace day。

#### P2
P2和P3是两人一组。没太听说过有一人一组的。最好在选课之前就找到志同道合想要一起作死的小伙伴。

P2写一个线程库。这个项目再次回到用户层级的代码,也是第一次接触15-410的kernel,在这个kernel提供的系统调用原语上实现线程的create/join/exit,实现一个互斥锁mutex library,实现一个condition variable library,一个semaphore library,一个reader/writer lock library。整个项目分配的时间为两周。

这个项目开始已经进入OS项目的精髓。P2和P3是所有项目中在成绩里占比例最大的两个项目,而且两者有依存关系,P2如果做得不尽人意,会对自信心打击很大,P3也没法完美完成。另外partnership也是很重要的需要注意的一个地方,俩人性格要能合得来,不然可以想象肯定特别煎熬。一起熬过了OS这种课之后也算是共患难了。

P2主要训练对栈的理解和操作能力,Synchronization,还有代码结构的设计技能。说到这里要讨论一下这个课程的名字:Operating System Design and Implementation。实现固然重要,但是Design也是很重要的一部分。这门课的Design是真正的让学生对自己的项目进行详细的设计,因为并没有一个guideline告诉你整个代码的架构应该是什么样的。比如mutex的实现,课上讲了许多错误的实现方法,而如何正确地实现mutex就需要你自己去设计,去考虑。在前几节课的开头,都会讲一些logistics,而第二节课的logistics就是如何问问题。基本上就是说,所有的问题分为几类,你在问教授或者助教这些问题之前要做一些功课,否则助教不会对你有什么帮助的。比如你直接去问助教,我mutex写不出来了,大体的设计思路应该是怎样的?他会回答你:这是一门Design and Implementation的课程,Design是课程要求的一部分,所以这个问题没办法回答你。再比如,你问助教:我想出了三种实现mutex的方法,哪种更好呢?他会说,你要做一个针对这三种设计的各种metrics的比较表格,然后综合考量之后,选择一个,并且在文档中写下你选择这个解法的原因。所以说,项目的实现是没有标准答案的,不同的设计哪个更好也是见仁见智,只要你能够进行有效的reasoning就没有问题。

还有一个重要的点就是找Race和规避Race的能力。这是P2收获最大,也是最恶心的地方。包括期中考试,找Race从来都是很重要的考点之一。讲Synchronization的时候会讲到一类叫做Paradise Lost的bug,是一种TOCTOU bug,P2和P3要尤其注意。

另外就是时间控制,两周时间对于P2来说稍微紧了一些,最好早一点把代码写完,留三到四天进行debug。我和partner写完了所有代码之后经历了一个bug,在跑一个测试的时候会出现不可重现的程序崩溃,最后发现是有一个线程有时候在另外一个线程的栈上执行导致了栈的内容corrupted,而导致这个bug的原因只是少写了一个check。如果真的是最后一天才把代码写完开始测试的话,P2基本告别及格。


#### P3 - kernel
神圣的Kernel Project,是这门课的巅峰。一共大约6到7周的时间。P3刚开始的时候是期中考试,所以我和partner P3的第一周基本报废。

P3的内容:写一个操作系统内核,实现所有要求的system calls。关键点主要在于虚拟内存管理,和进程和线程的管理和调度。P3需要用到P1所写的driver进行相应的驱动,还有P2所写的线程库对kernel的线程进行测试。P2没写好的话P3测试心里都没有底,测试失败了都不知道是P2代码的问题还是P3代码的问题。 课程提供了一个reference kernel,这个kernel在P2的时候用来跑测试,然后P3就是要做一个和reference kernel功能一样的kernel出来。

Simics是一个kernel level code debugging神器,我和partner在上学期用的最多的是它的reverse execution功能。而且可扩展性也不错,可以用python写脚本进行自动化测试或者环境初始化。P3的时候,我上学期写了一个python脚本实现了一个相当于`ps`命令的脚本,打印进程树,还是挺有用的。P3写的过程当中,除了用Simics进行调试,还可以把编译好的img文件放到VMware里作为软盘启动盘跑起来,由于Simics是对每一个硬件,每一条指令进行模拟,所以执行速度会很慢,然而同样的东西放到VMware上跑就会快很多,有bug的话也能很快跑出来。除了这两个方法之外,还可以将img文件做成软盘,到机房的crash box上面进行真机测试。

关于调试,P3的东西写完之后总会有各种bug不断浮现,而且会出现double fault和triple fault,而出现了double fault或者triple fault的时候,这就是你所有的信息了:你可能需要肉眼找到代码中可能出现问题的地方并且重新运行Simics并且加断点(执行/内存读写断点)进行调试;而在这中间,由于你还需要把Paging列入调试的考量范围之内,你还需要对内存地址的映射正确性做出假设。如果这个假设不存在的话,还要调试虚拟内存的正确性。Kernel当中的各个模块耦合度是很高的,可能少写了一行代码,甚至传错了一个参数的错误所导致的bug会花掉你和partner两个人3天的调试时间,不开玩笑。

Race的问题在P3的时候比P2更甚;P2好歹你能假设reference kernel所提供的所有system call的实现都是正确的(误,reference kernel也会有bug的),但是P3任何原语都没有,所有的东西都是你来实现。如何写出Synchronization完美的代码,是很重要的一部分。

P3在提交的时候,会有一个叫做P3 extra的东西,就是说如果到了P3该交代码的时候如果你还没有完成基本的kernel实现,你可以再用10天的时间完成你的kernel,然而代价是你将失去做P4的机会。之所以有P3 extra这种设定,是因为这门课最重要的就是P3,所以完成它是学生的top priority。在这里说一句,如果你的kernel只是有一两个小bug,比如跑某个test的时候偶尔会有一些不可重现的bug出现,我建议你不要选择使用P3 extra,而是去做P4。然而如果你的一些基础测试总是有bug出现,甚至shell都没法工作的话,那P3 extra可能是唯一的选择:因为如果你的P3有很大的问题,但为了做P4,还强行提交一个表格说你们的kernel没问题的话,很可能你们的P4就0分了,并且P3 extra的机会也丢掉了。如果你和partner真的对你们的代码不够自信的话,我的意见是仔细参考hurdle form,然后去问一下教授,他会告诉你你最好的选择是什么。

说说评分:在P3结尾的时候,老师会提到一些关于bug的问题:不要担心你提交的kernel当中有bug,所有的kernel都有bug,macOS有bug,Windows有bug,Linux Kernel也有bug,所以Pebbles当中有bug也是很正常的。只要你能够满足P3上对代码架构的要求,以及对所有明确要求不能做的事情的规避,至少能拿一个比较体面的分数。但是做到以上两条难度已经不小了。如果你的kernel真的没有bug的话,你可能有做TA的潜质。除此之外,还有一个点:如果已经提交了kernel,并且没有grace day了,并且交了之后发现你所提交的kernel有bug,那么教授可能会同意你交一些小的patch文件来对你的源代码打补丁。当然前提是不是大的design flaw,是那种一两行代码就能修好的bug。这点很人性化,也很不人性化,因为可能好不容易熬到了最后一天,然后发现交了kernel之后居然还要debug,有一种生无可恋的感觉。

P3结束之后已经进入学期末了,助教们不会给你一个像P0到P2那样详细的批改;取而代之的是一个叫做P3 interview的东西:一组两个人和批改你们kernel的那个助教进行二对一的交谈,助教问你们一些问题比如为什么这个地方你们是这么设计的,有什么考虑;有些地方应该如何实现而不是这么实现;代码中会出现一些bug,具体execution trace是怎么样的会触发这个bug。说实话,有些bug在写的时候真的是想不到,即使和其他组进行交流,彼此知道大致的corner case都有什么,还是会有一些你想不到的很偏的corner cases。但是这些cases的目的并不是把你的分数扣掉,而是让你以后写代码的时候思维更加严谨,这是很好的事。

#### P4
P3做完了,P4相当于饭后甜点(大雾)。P4每年的内容不一样,以前有写文件系统的,有要求把P3时没有要求写的system call实现了的。我们上学期是要求在P3所写的kernel上增加对Assymetric Multi Processor的支持;但是所有学期的P4都是在P3上加东西。P4的评分细则会很松,或者至少我们的体验是这样;另外可能P4结束的时候已经不再想去碰OS的源代码了,因为实在是看够了。所以Enjoy是很重要的。


### Exams
考试也是这门课很重要的一部分。期中考试主要考察对mutex的实现和Synchronization,往年的考试题会在课程网站上公开。考试时候的时间管理很重要,基本没有做完一道题然后回头再做一遍的时间。另外,Design那道题很重要,分数很多,不要写出死锁,不要写出Race,不要忘记初始化和destroy。期末考试考查方式和期中一样,找Race和Design的题都会有,还会问一些你对课程内容的深层理解,而不仅仅是表面的东西。值得一提的是期末考试没有样卷。

### Book report
说这门课奇葩不过分。在以上这么多内容之外,在期末的时候还要交一个读书报告。你需要在学期中选择一本书或者一类论文进行阅读,并且在学期末写一篇一到两页的读书报告,综合讨论一下这本书或这些论文的质量,哪些地方比较吸引你,哪些地方写的不好,等等。选什么书是你自己的决定,课程网站上有一个pre-approved list,里面的书都很不错,Effective Java也在其中。

## 一些感受
选了OS人生完整了。之前一直觉得自己在Systems方向的基础不够坚实,上了OS之后确实有了些底气,别人问到关于kernel一般问题的时候也能比较容易地回答出来。毕竟自己写过kernel和只是浅显地看过一本书,理解一下概念是不一样的。像最后一节课所说的一样,This is a transformative class.
  • 36
5条回复