外观
从逻辑门到应用:计算机的层次
同一个 a[i] += 1,可以被解释为语言表达式、机器指令、缓存访问、总线事务或逻辑门状态变化。每层抽象都选择一组稳定接口,让上层不用重新处理所有底层细节。学习系统需要知道接口在哪里,也需要知道哪些成本会穿透接口。
组合逻辑与时序状态
组合逻辑的输出由当前输入决定,例如多路选择器、加法器和比较器。时序电路还保留历史状态,寄存器在时钟边沿更新。ALU 本身不是一个会“记住程序”的黑箱;程序状态由寄存器、程序计数器和内存共同承载。
Nand2Tetris 用简化硬件描述语言搭建门、电路、CPU,再构建汇编器和编译器。这条路线适合看清层次,但教学 Hack 机器的结构不能直接当作现代 x86 或 RISC-V 微架构。现代处理器还有流水线、乱序执行、分支预测和多级缓存。
指令集是硬件与软件的契约
ISA 规定可见寄存器、指令含义、异常等架构行为;微架构决定用什么流水线、执行单元和缓存实现这些行为。同一个 ISA 可以有性能差异很大的 CPU。程序编译为某个 ISA,不意味着编译器知道所有机器上每条指令的固定耗时。
一次简化指令执行可分为取指、译码、读取操作数、执行、访存和写回。流水线让多条指令的不同阶段重叠;数据依赖、结构冲突和分支使重叠受限。乱序执行尽量让准备好的工作先做,但最终要维护架构要求的可观察结果。
flowchart TD A[应用与语言] --> B[编译器与运行时] B --> C[指令集与调用约定] C --> D[CPU 微架构] D --> E[寄存器与缓存] E --> F[内存与设备] A --> G[系统调用接口] G --> H[内核:隔离 调度 文件] H --> C
查看流程图文本
flowchart TD A[应用与语言] --> B[编译器与运行时] B --> C[指令集与调用约定] C --> D[CPU 微架构] D --> E[寄存器与缓存] E --> F[内存与设备] A --> G[系统调用接口] G --> H[内核:隔离 调度 文件] H --> C
内存层次把局部性转为速度
寄存器很近但容量小,缓存保存最近或邻近访问的数据,DRAM 容量更大但访问延迟更长,持久存储又有不同的吞吐和延迟。连续处理数组通常容易利用空间局部性;链表随机跳转可能使地址依赖串行化,让 CPU 无法提前发起下一次访问。
缓存命中与虚拟地址翻译是两个相关但不同的过程。TLB 缓存页表映射,数据 cache 缓存字节;一次访问可以 TLB 命中但数据 cache 未命中,也可以相反。虚拟内存提供隔离和映射抽象,不只是“内存不够时使用磁盘”。
多核各自缓存同一条缓存行时,还需要一致性协议管理修改权。即便两个线程更新不同变量,如果变量位于同一缓存行,也可能因假共享反复转移所有权。语言内存模型又规定哪些跨线程观察有定义;硬件有一致性协议不等于无锁普通变量读写就安全。
内核把硬件资源变成可管理的对象
进程封装地址空间、资源引用和执行状态;线程是调度执行的载体之一。系统调用把用户请求转给有特权的内核,内核检查参数和权限。设备通过 MMIO、DMA、中断等方式与 CPU 协作:CPU 可以发起工作后去执行其他任务,设备完成再通知。
文件描述符是进程描述符表中的整数索引,通过表项引用打开文件描述等内核对象,不是文件在磁盘上的地址。多个描述符可以引用同一个打开文件描述,例如 dup 后共享文件偏移。路径解析、权限、缓存、文件系统元数据、块设备驱动组成一条较长路径;write 返回与字节已经断电持久化也不总是同一时刻,应用需要根据 API 与文件系统语义选择同步操作。
把一条加法放回全栈
编译器把数组索引变为地址计算,CPU 发起虚拟地址访问,MMU/TLB 完成翻译,缓存层次返回数据,执行单元计算结果并保存。如果页面尚未映射,触发异常进入内核;如果是 COW 页,内核分配私有副本再重试;如果多线程共享该值,普通读改写不能保证原子性,需要合适同步。
这些不是互不相干的课程细节:它们是同一个程序在不同边界上的解释。性能分析也应沿路径寻找瓶颈,而不是只凭某一层的名词判断。
自测:为什么同一个程序可以在更高主频的机器上运行更慢?
主频只描述时钟节奏。指令数、每周期完成工作、缓存未命中、内存带宽、分支预测、并行度与功耗限制都会影响总时间。若主要等待内存,提高执行单元频率未必带来相同比例收益。
来源:Nand2Tetris 12 个项目、RISC-V ISA 官方手册。深入学习进入 CSAPP、操作系统与并行计算。