我想很多CMU的同学在选课的时候都搜到过关于15410/15605 Operating System Design and Implementation这门课的传说,比如这篇,比如这篇。BTW,我是从前面那篇介绍知道的这门课。以前学长们的介绍都挺详细了,我在这里就是记录一下我这个学期上这门课的体会吧,有很多地方与前人的介绍会有重复,毕竟这门课应该好久没有改过了😑。

为什么我要选这门课

在我一年多前看了这门课的介绍之后,我就决定一定要选上这门课。最吸引我的地方就是它的P3,也就是实现kernel的那个project。作为一个程序员,大家都是听着K&R写Unix,Linus写Linux的传说长大的(误),谁不想亲手写一个OS内核?毕竟是程序员三大浪漫之一。

另外一个原因是虽然我在国内读本科的时候也上过OS课,但是远远没有达到能手写Kernel的水平,我想绝大多数国内的本科教学都没有达到这个高度。自学的话也很困难,一方面是写Kernel难度确实大,另一方面是没有相应的toolchain来debug,毕业以后再想尝试的话就只有找Kernel相关的工作了,所以这门课是一个挺宝贵的机会。

再加上看到前人对这门课的难度评价这么高,就激起了我的好胜心,偏要来试试。所以我也把这门课当作一个试炼,来检验我近六年的编程学习能不能hold住手写Kernel。

课程介绍

接下来是无聊的课程介绍,只想看我的主观看法的可以往下翻到下一节去了。

选课

OS这门课不是有位置就能选的,它的waitlist不是一个FIFO结构,老师会手动的register,因此想选这门课的同学最好先上完三门system课(包括15213/15513/18600)。

老师

我是在刚刚过去的18 Spring上的这门课,老师是Dave EckhardtBrian Railing。Dave教这门课教了挺久了,Brian也在教15213,所以老师们的水平是很高的。

上面这个大胡子就是Dave……

这门课应该在Fall和Spring都有开,但是Fall应该会有两个section,总人数都在40多人左右。

Lectures

每周上三次课,每次50分钟。Lecture的内容其实跟国内的差不多,进程线程,中断,虚拟内存,同步,死锁,自举,IPC,任务调度等等。后半部分会有一些比较现代一点的东西(其实也不算现代了……)比如虚拟化和内核安全。因为在大约midterm的时候就要开始写kernel project,所以一些比较重要的东西的进度会很快(虚拟内存,同步,调度等等),如果是初次接触这些概念的话可能会要花费大量的时间在上课以外,这个要注意。

Projects

OS总共有5个Project,其中第三个和第四个是比较重要的,尤其以第四个Kernel project最重要。从第三个project开始是要两人的team来写。

P0

第一个Project很简单,写一个stack backtrace的函数,被invoke的时候把调用栈打印出来。主要考察的就是对x86 C程序的call convention的了解程度。

P1

第二个project是写一个跑在kernel space的小游戏,具体是什么游戏每年都在换,并不重要。主要是要编写键盘,时钟和console这三个驱动,实际操练一下怎么处理硬件中断。

P2

第三个project的难度就有点提高了,也是开始要团队合作了(然而我不一样……看下文)。这个project要在user space实现一个multi-thread library以及相应的同步原语(synchronous primitive),就是一个类似于简易pthread的东西。提供一个写好的kernel(这个kernel的绝大多数功能都是在p3要实现的),在这个kernel上面写library。你需要处理thread的创建,销毁和同步,还要实现mutex,condition variable,semaphore和read/write lock等同步机制。这个project的难点在debug上,并发的同步问题debug起来非常恶心,但这个恰恰是OS这门课要搞明白的核心问题,并发问题从p2开始就会一直阴魂不散直到你考完Final为止。

P3

大约在midterm之前的那一周,P3就会开始了,这个project的难度也体现在它的时间跨度上,单单这一个project就需要约50天,P2也只给了两周时间而已。

在这个project里,你要实现一个功能基本完整的抢占式分时多任务内核,主要包括:

  • 虚拟内存(使用二级页表)
  • 内存管理
  • 硬件驱动
  • 多进程,多线程支持
  • 时钟驱动的线程调度器
  • 约二十多个系统调用
  • 内核抢占
  • 处理faults/exceptions

我在写这个project的过程中,最困难的点就是实现一个优雅的上下文切换和内核同步了。Kernel在从一个thread切换到另一个thread的时候会进行上下文切换(context switch),这一部分需要保存thread的上下文(context),用汇编+thread的内核栈(kernel stack)可以非常优雅地实现它。而内核地同步机制需要在P2的同步原语的基础上做一些修改,因为P2依赖的是kernel的调度器,到P3调度器本身都会依赖这些原语,很容易就变成一个先有鸡还是先有蛋的问题。所以在P2的时候一定要尽量写得robust一点,不然一旦启用了时钟中断可能就炸的彻底。

这个Project的debug更恶心了,因为现在连一个可信的kernel都没有了,调试工具也不怎么好用,我面对各种死锁问题只能一点一点打log。另外,对于用汇编写的内存操作要极其小心地来安排,有时候会觉得是在捧着一大摞摇摇欲坠的玻璃杯,稍有不慎你辛辛苦苦写的kernel就死给你看。

我在这个Project上大约总共花了70-80小时的时间,代码量约在5000+左右,从三月初考完midterm一直到春假结束基本写完,然后浪了一个多月,所以50天还是非常充足的。

P4

P4只有一周多的时间,要在P3的基础上实现一个更高级一点的功能,每个学期要实现的东西都不同,这学期要写一个user space的go风格协程库(实际上是要在P2的基础上来修改,只不过要跑在P3你写的Kernel上)+一个类似于signal的软时钟中断。没有什么普适性,就不多说了。

成绩

OS有期中和期末两次考试,最后成绩主要就是两次考试+Projects,两次考试都固定有一题是要手写一个新的sync数据结构和函数。其他题基本是简答题和找deadlock之类的问题。我觉得我的考试考的并不是很好,所以最后并没有拿到A……(T_T),想拿A的话重点关注Projects和考试。

体会

难度

我想很多同学都是非常关心难度的问题,老实说,这是一个小马过河的问题,到底难不难你只有上过才知道。这门课我上下来觉得并没有传说中那么难,远远没有难到无法下手的程度,对我来说,可能没有一些Advanced XXX的读论文的课来的难能用代码解决的问题都不是问题,毕竟它只是一门本科生课程。

如果你本科的时候上过OS,或者对于自己的C语言功底有自信,那么我觉得选这门课还是很不错的。但是有一点要注意,这门课有15学分,不同于大多数课的12学分,而ECE的master有每学期48学分的选课限制,因此如果你一学期需要三门课来保持合法身份,那么选了这门课,你就没法再选一门课当作备份,也就是说不采取一些其他措施的话不能中途drop掉它。

Team

之前说了这门课从P2开始是组队写,然而我因为种种原因,错过了组队那节课,导致只能孤家寡人的solo所有project。我发现找不到partner之后有几天非常恐慌,觉得这门课很有可能要fail了,但是实际写下来,发现team和solo各自有利有弊。Team的好处是各种问题都有人可以讨论,不容易走入死胡同出不来,而缺点是两个人合作互相之间的沟通成本很高(仍然是一个同步问题),以及代码的质量问题会被放大。而Solo就不存在沟通的问题。

其他学到的东西

除了OS的知识以外,我想我还从这门课中学到了一些其他的东西。我觉得最重要的一条就是There is no magic,内核再复杂,也是像我这种普通人能力可及的范围,它不是魔法,这让我在以后面对复杂项目的时候不至于一下子被吓倒,我觉得这种信心是很重要的。

另一点就是一个好的设计可以成倍地节约debug时间,P4的时候我就没有仔细的重新设计结构,导致debug的时候到处都是问题并且重构成本极高。

这学期的其他课

为了OS,我这学期特意选了两门看上去不是很难的课,虽然最后实际上不需要这么小心翼翼ㄟ( ▔, ▔ )ㄏ。

17437 Web Application Development

就是从最基础的HTML开始学WEB开发,讲的东西都很基础,后端是用Django。说实话我并不是很推荐,因为这个课讲的东西实在是太基础了,完全是可以自学的。前端部分最难也就是到AJAX+jQuery,在现在的前端圈子看来大概是属于上古技术了。后端也就是写写models,forms,templates等一些很烦的东西。只有期末考试,选择题+简答题+手写Django应用题←没错也是手写。

14735 Secure Code

主要讲如何写安全的C/C++,少量的其他类型程序的安全性。这门课可以做ECE master的核心课。这门课有约一半的时间是在看汇编找漏洞并且构造攻击输入来达到运行特定函数的目的硬核黑客,虽然基本都是各种字符串溢出问题,但这一部分还是挺有意思的,剩下的时间会讲一些fuzzing,formal method之类的内容。没有考试。