2. 应用视角的操作系统

操作系统是连接软件和硬件的桥梁。因此想要理解操作系统,我们首先需要对操作系统的服务对象 (应用程序) 有更精确和深刻的理解——实际上这是这门课程最主要的内容。

本讲内容:程序的状态机模型;通过实例讲解操作系统上的程序、最小程序和应用程序。

2.1 操作系统上的程序

操作系统最大的职责就是让我们在编程时感受不到操作系统的存在——我们在编程时想象程序 “独占整个计算机,逐条指令执行”,大部分时候都不用 “考虑” 操作系统的存在。当系统调用发生时,程序执行被完全暂停,但操作系统依然在工作——就像麻醉后醒来,周围的环境发生了变化,但我们完全没有感到时间的流逝。

💬
Prompt: 用 C 语言实现非递归汉诺塔,从模拟程序执行的角度,得到和递归程序一样的程序执行流,但不能使用递归。

2.2 操作系统上的最小程序

💬
Prompt: 我即便写一个空的 main(),链接后依然生成了很大的代码。怎么才能实现最小的 C 程序呢?

系统调用是本课程最重要的概念,你可以这么理解:操作系统就是程序的上帝,通过想好想让上帝做什么 (根据 ABI 设置好寄存器的值),然后开始祈祷 (执行系统调用对应的指令),然后上帝会立即显灵,完成这个任务。比如,如果程序想的是 exit(),整个程序世界就会立即湮灭。让我们创造一个小说吧:

💬
Prompt: 【上面的背景...】效仿刘慈欣的写作手法,写一个小说

2.3 操作系统上的应用程序

💬
Prompt: 我有一个 a.out 文件,如何探索它里面有什么?
💬
Prompt: 我感觉 strace 的结果太冗长,如何精简,提高可读性?

2.4 总结

Take-away Messages: Everything (高级语言代码、机器代码) 都是状态机;而编译器实现了两种状态机之间的翻译。无论何种状态机,在没有操作系统时,它们只能做纯粹的计算,甚至都不能把结果传递到程序之外——而程序与操作系统沟通的唯一桥梁是系统调用 (例如 x86-64 的 syscall 指令)。如此重要的桥梁,操作系统中自然也有工具:strace 可以查看程序运行过程中的系统调用序列。

课后习题/编程作业

📚阅读材料

本次课没有书本阅读材料;但你可以花一些时间了解一下相关的源码项目:

  • 浏览 GNU CoreutilsGNU Binutils 的网站,建立 “手边有哪些可用的命令行工具” 的一些印象。
  • 浏览 gdb 文档的目录,找到你感兴趣的章节了解,例如——“Reverse Execution”、“TUI: GDB Text User Interface”……
  • 对于你有兴趣的命令行工具,可以参考 busyboxtoybox 项目中与之对应的简化实现。Toybox 现在已经成为了 Android 自带的命令行工具集。
🖥️编程

在你的 Linux 中运行课堂上的代码示例,包括:

  • 编译链接最小二进制文件,并使用 strace 查看执行的系统调用序列。
  • 尝试将非递归汉诺塔扩展到 ffgg 两个函数互相调用的情况。
  • 使用 strace 查看更多程序的系统调用序列,并在好奇心的驱使下了解一部分系统调用的作用。

在之后的课程中,我们默认你会运行和理解课堂上的代码示例。