跳转至

抽象层次与接口契约

动机:复杂性必须被切开

数十亿晶体管不可能由应用程序员逐个控制。工程上的办法不是消灭复杂性,而是建立边界:上层只依赖下层承诺的行为,下层可以在不破坏承诺的前提下更换实现。这就是抽象。

常见层次可以写成:

应用与算法
高级语言、编译器、操作系统
指令系统结构 ISA
微体系结构:数据通路、控制、流水线、cache
数字逻辑:门、组合电路、时序电路
器件与工艺:晶体管、连线、存储单元

箭头不是单向“翻译”。异常从硬件向操作系统报告,性能计数器向软件暴露微体系结构现象,编译器也会依据流水线和 cache 特性安排代码。

接口与实现

接口说明使用者可依赖什么,实现说明承诺怎样兑现。以加法指令为例,ISA 会规定操作数来自哪些寄存器、结果截断到多少位、程序计数器如何变化;它通常不规定这次加法由一拍完成、分成多级流水,还是与其他指令乱序重叠。

这带来两个好处:

  • 软件生态可跨处理器代际复用;
  • 硬件设计者可以用面积、功耗和性能之间的不同权衡实现同一语义。

代价是接口一旦广泛部署就很难修改。历史兼容性会让旧指令、旧异常行为和旧地址模式长期存在。

最小完整例子:z = x + 1

假设 xz 位于内存,编译器生成简化指令:

lw   x5, 0(x10)   # x5 <- Mem[x10]
addi x5, x5, 1
sw   x5, 4(x10)   # Mem[x10 + 4] <- x5

各层分别做什么?

层次 对这个例子的职责
语言 规定整数加法和溢出的语言语义
编译器 选择 lw/addi/sw,分配寄存器
ISA 规定三条指令的编码与可见效果
微体系结构 取指、译码、计算地址、访问 cache、写回
数字逻辑 用加法器、多路选择器、寄存器和状态机实现数据流
器件 在电压、时序和功耗约束下实现逻辑 0/1

这个例子“麻雀虽小,五脏俱全”:有指令、有状态、有计算、有访存,也有潜在的流水线相关和 cache miss。第 8 章会把它展开成逐周期路径。

架构状态与瞬时状态

程序正确性关注架构状态:通用寄存器、程序计数器、内存和体系结构定义的控制状态。处理器内部还有预测表、流水寄存器、重排序缓冲区等微体系结构状态。发生分支预测错误时,瞬时状态可以被清空,但已经提交的架构状态不能被错误路径污染。

这一区分与数据库事务很像:内部可以投机执行,但“提交”前不能让外界观察到不一致结果。

抽象会泄漏

抽象保证功能语义,不必保证恒定性能。数组按行访问和按列访问在语言层等价,却可能因 cache 局部性产生巨大差别;浮点加法满足 IEEE 754 语义,却不满足实数加法的结合律。安全领域中的侧信道更直接利用了时间、cache 或功耗这些原本未写进功能接口的信号。

因此更准确的直觉是:抽象建立可依赖的最低契约,而不是让下层永远不可见。

与其他课程的联系

  • 数据结构讨论访问模式,组成原理解释访问模式为何影响 cache。
  • 编译原理把高级程序映射到 ISA,并做机器相关优化。
  • 操作系统管理异常、中断、页表和设备,但依赖硬件提供机制。
  • 数字电路提供组成部件,组成原理解释部件怎样构成可编程机器。

自测

  1. “x86 是一种微体系结构”这句话哪里不准确?
  2. 分支预测器出错为何通常只影响性能,不改变正确结果?
  3. 列举一个功能抽象没有泄漏、但性能抽象发生泄漏的例子。