欢迎来到系统软件(A Level 9618)

你好,未来的计算机科学家!本章系统软件将带你揭开幕布,看看计算机在后台到底是如何运作的。

我们通常会想到各种应用程序(即应用软件),但如果没有强大的系统软件在背后管理一切,这些程序都无法运行。在 A Level 阶段,我们将深入研究操作系统 (OS) 的运行机制,以及语言翻译程序所使用的重要流程。

别担心,“分页”或“词法分析”这些概念听起来很复杂——我们将通过简单的类比来拆解它们,确保你掌握考试所需的这些核心知识!

16.1 操作系统的目的 (OS)

什么是操作系统?

操作系统 (OS) 是管理所有计算机资源(CPU、内存、文件和外设)的总控程序。它充当了硬件与用户/应用程序之间的中间人。

OS 功能 1:最大化资源利用与隐藏复杂性

操作系统的首要工作是最大化资源利用率(如 CPU 时间和内存空间)。

  • 用户界面: 操作系统提供用户界面(如 Windows 或 macOS),让你能够通过简单的图标和菜单与计算机进行交互。这个界面隐藏了硬件的复杂性
  • 类比: 想象一下驾驶汽车。你只需要使用方向盘和踏板(界面)。你不需要了解内燃机的复杂物理原理或齿轮是如何啮合的(隐藏的硬件复杂性)。
OS 功能 2:进程管理(多任务处理)

当你打开多个程序(例如:播放音乐、写论文、运行计算)时,操作系统通过进程管理来处理这些并发任务。

进程 (Process) 就是当前正在运行的程序。操作系统使用多任务处理 (Multi-tasking) 让所有进程看起来像是在同时运行,但实际上,CPU 是在它们之间快速切换。

进程状态

一个进程可能处于以下三种基本状态之一:

  1. 运行 (Running): 进程当前正在 CPU 上执行指令。(在任何时刻,每个 CPU 核心只能真正运行一个进程)。
  2. 就绪 (Ready): 进程已准备好执行,但正在等待 CPU 可用。
  3. 阻塞 (Blocked): 进程无法继续,因为它在等待某个外部事件发生(例如:等待键盘输入或从硬盘读取数据)。

小技巧: 记住 R-R-B(Running, Ready, Blocked)。

调度 (Scheduling) 的必要性

为了管理下一个获得 CPU 使用权的进程,操作系统会使用调度程序 (Scheduler)调度例程。调度的目标通常是最大化吞吐量、确保公平性并减少响应时间。

你需要理解以下调度算法的功能及其优缺点:

  • 轮询调度 (Round Robin, RR): 每个进程在 CPU 上获得固定的、很短的时间片 (quantum)。如果时间片用完,进程将移动到队列末尾。
    • 优点: 对交互式用户而言,具备极佳的公平性和响应时间。
  • 短作业优先 (Shortest Job First, SJF): 预计执行时间最短的进程优先运行。
    • 优点: 最大化吞吐量(能快速完成更多任务)。
  • 先来先服务 (First Come First Served, FCFS): 最先请求 CPU 的进程获得优先权。
    • 优点: 实现简单;非常公平(不会出现饥饿现象)。
  • 最短剩余时间 (Shortest Remaining Time, SRT): SJF 的抢占式版本。如果新到达的任务总时间比当前运行任务的剩余时间短,新任务会抢占 CPU。
    • 优点: 比 SJF 具有更高的吞吐量。
OS 功能 3:内核与中断

内核 (Kernel) 是操作系统的核心组件。它在计算机启动时首先被加载,负责处理最关键的底层任务,如内存管理和中断处理。

内核充当中断处理程序 (Interrupt Handler)中断 (Interrupt) 是发送给 CPU 的信号,用于临时停止当前正在运行的进程,以便 CPU 处理高优先级事件(如磁盘错误、I/O 完成或定时器信号)。

  • 当中断发生时,内核会保存当前运行进程的状态(使用程序计数器 PC 和累加器 ACC 等寄存器)。
  • 内核识别中断类型。
  • 执行相应的中断服务例程 (ISR)
  • ISR 完成后,内核恢复原始进程的状态,进程从中断处继续执行。
OS 功能 4:虚拟内存、分页与分段

虚拟内存 (Virtual Memory) 是一种技术,操作系统利用辅助存储器(硬盘)来模拟额外的 RAM,从而允许运行比物理 RAM 更大的程序。

操作系统管理内存(特别是虚拟内存)的两种主要方法是分页和分段:


分页 (Paging)
  • 概念: 主存 (RAM) 被划分为固定大小的块,称为页框 (Page frames)。程序被划分为固定大小的块,称为页 (Pages)
  • 当程序需要内存时,页面会在磁盘和 RAM 之间交换。
分段 (Segmentation)
  • 概念: 程序被划分为逻辑上大小不等的块,称为段 (Segments)(例如:一个段存放主程序代码,一个存放数据,一个存放子程序)。
  • 段在磁盘和 RAM 之间交换。
  • 区别: 页是物理的且大小固定;段是逻辑的且大小可变。

磁盘抖动 (Disk Thrashing)

这是一个关键术语!如果操作系统花费在页面/段在 RAM 和硬盘之间交换的时间,比执行程序指令的时间还要多,这被称为磁盘抖动

  • 结果: 系统速度大幅下降,因为 CPU 持续在等待缓慢的磁盘访问。
  • 页面置换: 如果操作系统需要空间给新页面,它必须替换现有的页面,通常选择最近未使用的页面(最近最少使用算法 – LRU),以避免换出频繁需要的页面。
16.1 核心要点

A Level 的侧重点在于多任务处理和内存管理的*机制*。请记住三个进程状态 (R-R-B),以及分页(固定、物理)与分段(可变、逻辑)之间的核心区别。

16.2 翻译软件

源代码(由人类使用高级语言如 Python 编写)必须转换为机器码(二进制指令),CPU 才能执行。这就是翻译软件的任务。

回顾:汇编程序、编译程序与解释程序

  • 汇编程序 (Assembler):汇编语言(低级、人类可读的指令)翻译成机器码。
  • 编译程序 (Compiler): 在执行之前将整个高级语言程序翻译成机器码。生成可执行文件。(属于 AS Level 内容,但它是核心先决知识)。
  • 解释程序 (Interpreter): 逐行翻译并执行高级语言程序。它*不会*生成独立的翻译版本。

你知道吗? 像 Java(控制台模式)这样的语言采用了混合方式:它们首先被编译成中间字节码,然后由 Java 虚拟机 (JVM) 进行解释。这就是为什么有些程序被描述为部分编译、部分解释的原因。

编译过程:四个阶段(A-Level 深度)

程序编译时会经历几个不同的阶段,以确保程序的正确性和效率。

第 1 阶段:词法分析 (Lexical Analysis)
  • 目的: 将源代码分解为有意义的单位,称为标记 (Tokens)
  • 去除注释和空格。
  • 识别关键字(如 IF, WHILE)、标识符(变量名)、常量和运算符。
  • 标记存储在符号表 (Symbol table) 中。
  • 类比: 阅读句子并识别单词,按空格将它们分开。
第 2 阶段:语法分析 (Syntax Analysis)
  • 目的: 根据语言的语法规则检查程序结构。
  • 使用第 1 阶段生成的标记来构建语法树 (Parse tree)(或抽象语法树)。
  • 如果发现错误(如缺少分号或括号),编译器会停止并报告语法错误
  • 类比: 检查句子结构在语法上是否正确(例如:“猫坐在垫子上”是正确的,而“猫子垫在坐上”则是语法错误)。
第 3 阶段:代码生成
  • 目的: 如果语法正确,编译器会将语法树翻译成中间代码,并最终生成目标机器码(或汇编代码)。
第 4 阶段:优化
  • 目的: 检查生成的机器码,使其运行更快或占用内存更少(即提高效率)。
  • 示例: 如果一个变量被赋值但后续从未被使用,优化器可能会删除那行代码。

表达语言语法

为了执行语法分析,编译器需要描述合法程序外观的精确规则。这些规则被称为语言的语法,并使用形式化方法表示:

巴科斯-诺尔范式 (BNF)

BNF 是一种元语言(用于描述另一种语言的语言),用于表达编程语言的语法。

  • 使用 ::=(定义为)、|(或)以及尖括号 < > 表示非终结符(需要进一步定义的元素)。
  • 示例: <digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
语法图 (Syntax Diagrams)

语法图是语法规则的图形表示。它们像流程图一样,显示了构建语言有效部分(如有效的标识符或循环结构)必须遵循的路径。

  • 优点: 与 BNF 相比,人类通常更容易阅读和遵循。

逆波兰表示法 (RPN)

在计算数学表达式时,计算机更倾向于使用一种无需括号和运算符优先级规则(BODMAS/BIDMAS)的方法。这种方法就是逆波兰表示法 (RPN),也称为后缀表示法。

在 RPN 中,运算符跟在操作数(数字)之后。

  • 标准(中缀): \(10 + 5\)
  • RPN(后缀): \(10 \ 5 \ +\)

RPN 如何求值(使用栈):

RPN 表达式通过栈 (Stack)(一种后进先出数据结构)来计算。

  1. 从左到右扫描表达式。
  2. 如果项是数字(操作数),则将其压入栈中。
  3. 如果项是运算符,则从栈中弹出必要数量的操作数(通常是两个),执行运算,并将结果推回栈中。

示例演示: 计算 \(5 \ 3 \ + \ 2 \times\)

第 1 步: 压入 5。栈:[5]
第 2 步: 压入 3。栈:[5, 3]
第 3 步: 运算符 '+'。弹出 3 和 5。计算 \(5 + 3 = 8\)。压入 8。栈:[8]
第 4 步: 压入 2。栈:[8, 2]
第 5 步: 运算符 '\(\times\)'。弹出 2 和 8。计算 \(8 \times 2 = 16\)。压入 16。栈:[16]
结果: 16

优点: 表达式按照读取顺序直接计算,简化了编译器内部计算所需的代码。

16.2 核心要点

请记住编译的顺序(词法 -> 语法 -> 代码生成 -> 优化)。理解 RPN 使用栈来计算表达式而无需括号,这对高效的计算机处理至关重要。