from mosaic import *
OS2023(2)
Changelog & 反馈
背景回顾:操作系统有三条主线:“软件 (应用)”、“硬件 (计算机)”、“操作系统 (软件直接访问硬件带来麻烦太多而引入的中间件)”。想要理解操作系统,对操作系统的服务对象 (应用程序) 有精确的理解是必不可少的。
本讲内容:指令序列和高级语言的状态机模型;回答以下问题:
slideshow('2.1')
demo('minimal', 'i/minimal')
操作系统使得应用程序有 “独占整个计算机,逐条指令执行” 的假象。如果把程序类比成我们自己的意识,感知到的时间似乎是连续不间断的。操作系统上的应用程序通过系统调用指令与操作系统交互,此时程序执行被完全暂停,但操作系统依然在工作——就像麻醉后醒来,周围的环境发生了变化,但我们完全没有感到时间的流逝。
因此,我们对操作系统上程序的一个很重要的理解是程序是计算和系统调用组成的状态机;大部分计算指令都是确定性 (deterministic,在相同环境下执行的结果总是相同) 的,少部分指令 (如 rdrand 返回随机数) 则有非确定的结果。系统调用指令是非确定性的,操作系统可能会将计算机运行环境中的信息,例如来自设备的输入传递到程序中。
slideshow('2.2')
demo('hanoi', 'i/hanoi')
对于任意的 C 语言代码,我们都可以把它解析成语法树的结构 (类似于表达式树,在《计算机系统基础》的 Programming Assignment 中包含了类似的实验)。C 程序的语义解释执行 “一条语句” 的更严谨说法是解释执行当前语句中 “优先级最高的节点”。
此外,我们也可以用类似汉诺塔的方法把 C 语言改写成仅包含顺序执行的简单语句和条件跳转 if (cond) goto
的 “简化 C 语言代码”——循环、分支和函数调用都被消灭了。这时候,我们的 C 代码已经可以逐条翻译成汇编指令了。这也是为什么 C 语言被称为 “高级的汇编语言”——我们几乎总是能从 C 语言的语法层面对应到机器指令序列。
slideshow('2.3')
demo('opt', 'i/compiler-opt.c')
如果你感到困难,不要慌,对于这些简单的例子,你完全可以让 ChatGPT 来帮你解释,例如问问他编译器可以对 return_1
作出什么样的优化,你不仅能得到正确的回答,还学到了一个新名词:
我们甚至有可能 “证明” 编译器所作出的优化全部是正确的,或是用更激进地方式生成更优化的代码,例如把几个行为已知的系统调用 “合并” 起来。如果你对这方面的知识感兴趣,我们有一些论文,供有基础且希望在这方面深入学习的同学参考 (读个大概即可):
Note: PL 领域 (的很多人) 有一种倾向:用数学化的语言定义和理解一切 (all about semantics);这 (一定程度上) 是因为 PL 在创立时是逻辑学的分支。(所以你看一眼 paper 就觉得自己瞎了。) 当然,这一切背后的直觉依然是 system/software 的:我们是人,不是无情的数学机器。
slideshow('2.4')
demo('strace', 'i/strace')
strace 是一个非常重要的命令行工具,帮助我们 “观测” 应用程序和操作系统的边界。实际上,任何程序的执行就是状态机在计算机上的运行,因此 “用合适的方式观测状态机执行” 就是我们理解程序的根本方法。调试器、trace、profiler 提供了不同侧面的理解手段,这三个工具将会在课程中反复出现。
如果你感到 strace 的结果不那么友善,用适当的工具处理它就非常重要了。课堂上我们展示了用命令行工具进行处理的 “传统方法”:
$ strace ls |& grep -e read -e write
可以实现系统调用的过滤等。未来我们身边的 AI 将会根本性地改变我们消化信息的方式:我试着让 ChatGPT 解释一段 ls
命令的 strace 输出。可以说它十分完美地 “消化” 了互联网上的文档,并把最重要、最相关的内容呈现给我们。这对于学习 “任何东西” 都是革命性的。这是对使用系统调用的解释: