现代操作系统设计背后的思路以及思路的核心指导原则

生存指南核心指导原则

不要恐慌。(不要惊慌)- 银河系漫游指南

OSLabs 曾经是一个可怕的传说。但随着jyy周数的增加,感觉越来越能在“挑战”和“有趣”之间找到平衡像素生存者2全部代码,让大家活下去:

但无论如何,花点时间谈谈如何在这门课中生存下去,因为操作系统毕竟还是一门非常硬核的编程课。

1. 为什么操作系统难学?

操作系统难学的主要原因是操作系统中的主题很多,其中有些不是大家熟悉的。例如,到目前为止,学生们编写的大部分代码都是串行的。比如写一个程序来模仿“一个人”,一次执行一个动作。但是操作系统引入了并发编程,当你需要协调共享内存的“多人”时,你会遇到很多你可能没想到的问题。

在操作系统中,即使我们不关心如何实现一个“低延迟、高吞吐量的现代操作系统”,即使我们实现了一个最小的工作操作系统内核,你仍然会遇到许多非常具有挑战性的问题。:

那些为解决这些问题做出杰出贡献的人获得了$n$ 图灵奖——并非每个问题都是素食主义者。掌握这些问题、解决方案和代码实现的来龙去脉确实非常具有挑战性。

2. 如何学习操作系统?

幸运的是,我们通过多年的经验找到了理解操作系统的两把“钥匙”,分别打开了操作系统的两面。

《程序眼中的操作系统》:对象+API

操作系统为应用程序提供了执行的底层环境、一组操作系统对象和操作它们的 API——这些东西被精确定义并且触手可及,至少对于今天的操作系统而言。我们可以通过代码片段、调试工具、日志、跟踪等方式真正触及操作系统必须在课堂上提供应用程序的所有内容。

这有助于我们理解现代操作系统设计背后的思想。

我们将了解什么是真实操作系统中的程序,以及如何借助操作系统提供的 API 对操作系统进行编程。具体来说,我们选择Linux(准确的说是POSIX)作为课程的教学平台,因为它既免费又免费(知道它的内部实现),而且互联网上有大量的文档(英文)。它的设计继承自“保持简单,愚蠢”的 UNIX,这种经典设计背后的动机对于刚接触操作系统的初学者来说更容易理解。让我们看看它的威力:

这使我们能够以比以往任何时候都更容易的方式来玩操作系统中的几乎任何东西。你想看看你磁盘的主引导扇区到底是什么代码吗?这很容易做到,只需一个命令即可:

cat /dev/sda | head -c 512 | ndisasm -b 16 -

您可以将其用作“高级用户”,而无需阅读太多手册。本课程将继续为您带来一些有趣的小惊喜,例如向您展示您周围的许多工具的代码——它们通常具有简单易懂的实现。例如,busybox 早期版本的 vi 实现只有一个文件。虽然这个文件有 3993 行,但是如果你使用正确的工具来折叠函数代码,你会发现它很容易,你可以编写它。

包括为了帮助您更好地理解 UNIX/Linux 操作系统的设计(即操作系统为应用程序提供了哪些对象以及操作这些对象的 API),我们在 Linux 系统上建立了多个小​​程序设计实验室,每行代码200行,实际调用Linux操作系统API完成一些仅靠C标准库难以完成的任务。

《硬件眼中的操作系统》:程序(​​状态机)

如果您完全了解操作系统中的对象以及如何操作它们,那么就剩下一个问题:您是否可以使用计算机硬件提供的机制来实现这些对象和API?事实上,算术和内存访问指令、I/O、中断/异常和虚拟内存都是我们实现操作系统所需要的。

通过阅读代码并调试真正的 OS 内核实现,我们终于了解了 OS 的全部内容。

UNIX 从一开始就被模仿——一个成功的例子是 Linux,在它之上还有可能更成功的 Android。当然,还有更多“迷你”的 UNIX 替代实现可以轻松帮助您理解代码背后的原理。我们选择xv6-riscv作为例子来讲解课堂上的操作系统。同时,大家还需要从操作系统实验室的“裸机”编程开始,自下而上实现一个支持多处理器、文件系统、虚拟存储的迷你操作系统内核。

3. 操作系统课程代码3.1 迈出第一步

图片[1]-现代操作系统设计背后的思路以及思路的核心指导原则-老王博客

如果您还没有开始并且仍然感到害怕,请记住:坚持下去,进入未知领域,从简单易懂的事情开始,投入时间,你会得到回报。参考资料中有一些很棒的介绍性材料,例如“Harley Hahn 的 Unix 和 Linux 指南”,非常吸引人(这本书不是为计算机专业人士编写的,所以它非常容易阅读并且有中文版)。从这里,你可以慢慢克服你的恐惧。

同样,您可能会发现很难理解一个小程序(例如类中的示例代码)。程序难以阅读是正常的——但程序的运行时状态是可以理解的。它只是数字和指针。请勇敢地打开您的调试器,设置断点,并逐步执行您的程序。不知道怎么调试?调试时没有代码?你需要万能的互联网。

我们为您准备了一些阅读材料。如果您能借助互联网手册了解以下“自测”内容,“操作系统”就非常适合您!

3.2 自测:C 编程

时至今日,本课程仍在使用 C 语言。它更简单,负担更少,并且在解释操作系统原理方面没有庞大的工具链。虽然说这相当于“手脚捆绑编程”,但我们通常不需要非常复杂的数据结构和代码逻辑,因此现代语言特性带来的好处大多是微不足道的。使用 C 语言还有一些额外的好处:

一个例子是“面向对象”,我们也可以在 C 中实现

struct foo {
  int (*bar)(struct foo *this, int a, int b); // 函数指针
};
void baz() {
  struct foo *ptr = get_object();
  ptr->bar(ptr, 3, 4);
  // 等效于C++: ptr->bar(3, 4)
}

C++ 中的对象确实是以这种方式实现的(类似地)。如果我们要实现动态绑定(使用父类的指针调用子类的方法),只需要把虚函数的入口放到一张表中,通过查找就可以得到函数的实际入口地址上桌:

struct object_header {
  void **vptr;
};
struct foo {
  struct object_header header;
  ...
};
void baz {
  struct foo *ptr = get_object();
  // ptr->bar(3, 4), dynamic binding
  // INDEX_OF_BAR在编译时由编译器确定
  (int (*)(void *, int, int)) (ptr->header.vptr[INDEX_OF_BAR]) (ptr, 3, 4);
}

如果你能看懂上面的例子,说明你已经有了扎实的C语言基础。如果没有,建议您阅读参考书中推荐的“The C 编程语言”。

思考题:多重继承

C++支持多重继承,即一个类可以有两个父类。你想过多重继承的实现方式吗?多重继承的实现是同时嵌入多个父类(会有两个header)。编译器将负责调用、动态绑定等。

从写C程序的角度来看(操作系统也是运行在计算机硬件上的C程序),所谓“编程”就是利用那些编程语言提供的机制像素生存者2全部代码,把内存中的数据取出来,计算,然后放回去。计算机系统没有魔法。

只有在泥潭中挣扎多年,一次次奄奄一息,才能体会到C++11/14/17/20+、Rust、Go等现代编程语言的美好初衷。

3.3 自我评估:编程技巧

学过《计算机系统基础》的同学一定经历过调试bug的噩梦。无数次你打算放弃(或实际上放弃),或者选择不拿到实验室/PA的结果,或者选择抱大腿实现你的同学……你做出的妥协是可以理解的,但过程中你忽略的调试经验,只会让你调试越来越大的系统越来越难。

我们的自测问题是:如果你在一个大项目中发现了一个 bug(假设你可以稳定地重现它),你应该如何找出问题所在?

如果您对这些问题有一些答案,那么恭喜!如果您一直在使用“蛮力”调试,那么您可能仍然可以处理旧代码,但对于规模增长的代码,它就无法正常工作了。

思考题:如何尽可能写出正确的代码?

在“为 OJ 编程”成为习惯之后,编程只不过是一个“测试 $\to$ 未被 $\to$ 修改”的循环。但是如果你想说服别人你的代码是正确的,你能做什么?

试着提前考虑这个问题。在本学期,您将面临多线程并发编程。这时,程序的正确性就显得尤为重要。仅仅依靠“盲测”是不够的。

4. 其他常见问题解答

操作系统可以用Golang/Rust/C++来实现,不过现在看来大家的包袱有点重了。

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论