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 Coreutils 和 GNU Binutils 的网站,建立 “手边有哪些可用的命令行工具” 的一些印象。
- 浏览 gdb 文档的目录,找到你感兴趣的章节了解,例如——“Reverse Execution”、“TUI: GDB Text User Interface”……
- 对于你有兴趣的命令行工具,可以参考 busybox 和 toybox 项目中与之对应的简化实现。Toybox 现在已经成为了 Android 自带的命令行工具集。
🖥️编程在你的 Linux 中运行课堂上的代码示例,包括:
- 编译链接最小二进制文件,并使用 strace 查看执行的系统调用序列。
- 尝试将非递归汉诺塔扩展到 f 和 g 两个函数互相调用的情况。
- 使用 strace 查看更多程序的系统调用序列,并在好奇心的驱使下了解一部分系统调用的作用。
在之后的课程中,我们默认你会运行和理解课堂上的代码示例。