20.1 C语言实现操作系统的优劣势

今天我们要讨论使用高级编程语言实现操作系统的优点和代价。今天课程的内容主要是我们之前写的一篇论文,论文作者包括了Robert和我,以及一个主要作者Cody Cutler,他是这门课程的一个TA。我通常不会讨论我们自己写的论文,但是因为这篇论文与课程内容相关,所以它在这。今天我们会用一些PPT,而不是在白板上讲解这节课。

这篇论文的起因是这样一个问题:你应该用什么样的编程语言来实现操作系统内核?这个问题很多同学都问过,可能是因为你发现了在操作系统中有Bug,然后你会想,如果我使用一种其他的编程语言,或许我就不会有这些Bug了,所以这是一个经常出现的问题。虽然这也是操作系统社区里的一个热烈争论的问题,但是并没有很多事实来支撑这里的讨论。在课程结束的时候,我们其实也不能对这个问题有个干脆的回答。但是至少这篇论文贡献了一些数据使得你可以对应该使用什么样的编程语言来实现内核这个话题,有一些更深入的讨论。这是这篇论文的出发点。

为了能回答上面的问题,我们使用了一个带自动内存管理的编程语言写了一个新的内核,所以现在内核带了Garbage Collector,你不用调用free来释放内存,这样就可以避免一类Bug。GC是高级编程语言通常都带有的属性,我们选择了一种带GC的编程语言,并且实现过程中我们遵循了传统的monolithic UNIX风格,这样我们才能做出公平的对比。实际上,从某些角度你可以认为我们创建的是类似于XV6的内核,但是拥有了更多的特性和更高的性能。你知道的,XV6中里存在各种O(n^2)算法和O(n)的查找,如果你想要写一个高性能的操作系统,你不能有这样的实现。以上就是论文的起因,以及我们创建Biscuit(也就是上面提到的操作系统)的原因,我们想要回答上面的问题,或者至少给出一些启发。

今天这节课首先我要讨论一些通用的背景,之后我们会深入到Biscuit的细节中。

现在很多操作系统都是用C实现的,你知道的,XV6是用C写的,一些更流行的运行在你的电脑、手机的操作系统,例如Windows,Linux,以及各种形式的BSD都是用C写的。

为什么它们都是用C实现的呢?

  • 首先C提供了大量的控制能力,从我们的实验中你可以看到,C可以完全控制内存分配和释放

  • C语言几乎没有隐藏的代码,你几乎可以在阅读C代码的时候想象到对应的RISC-V机器指令是什么

  • 通过C可以有直接内存访问能力,你可以读写PTE的bit位或者是设备的寄存器

  • 使用C会有极少的依赖,因为你不需要一个大的程序运行时。你几乎可以直接在硬件上运行C程序。你们可以在XV6启动过程中看到这一点, 只通过几行汇编代码,你就可以运行C代码

以上就是C代码的优点,也是我们喜欢C语言的原因。但是C语言也有一些缺点。

在过去几十年已经证明了,很难写出安全的C代码。这里存在各种各样的Bug,首先是最著名的buffer overrun,比如说数组越界,撑爆了Stack等等。其次是use-after-free bugs,你可能会释放一些仍然在使用的内存,之后其他人又修改了这部分内存。第三,当线程共享内存时,很难决定内存是否可以被释放。其中一些Bug在XV6中已经出现,其他的一些不太常见。因为XV6很少有动态内存分配,几乎所有的东西都是预分配的,所以共享内存Bug很少出现,但是buffer overrun和use-after-free bugs的确出现过。

CVEs一个跟踪所有的安全漏洞的组织,如果你查看他们的网站,你可以发现,在2017年有40个Linux Bugs可以让攻击者完全接管机器。很明显,这些都是非常严重的Bugs,这些Bug是由buffer overrun和一些其他memory-safety bug引起。这就太糟糕了,如果你用C写代码,就很难能够完全正确运行。当然,我可以肯定你们在之前的实验中都见过了这些Bug,之前在课程论坛上的一些问题涉及了use-after-free Bug。特别是在copy-on-write lab中,这些问题出现了好几次。

Last updated