用一个三条指令的教学模型,连接数据编码、状态变化与输入输出;说明程序可运行和系统可用之间还差哪些条件。
计算机为什么不只是算得快的机器
一台机器能做加法,还不足以说明它为什么能编辑文字、播放声音或浏览网页。关键是:不同对象能按规则表示为数据,同一套可编程机制能依据指令变换数据,再通过输入输出连接外部环境。
本文讨论数字电子计算机。它通常有处理器、存储器和输入输出设备;程序规定要执行的操作,存储状态保存当前的数据与进度。计算机不是直接认识一张照片或一句话,而是在具体编码、软件与设备协议下处理表示。模拟计算机、量子计算等不在本篇模型范围内。
Nand2Tetris 的计算机体系结构课程把CPU、内存与映射到地址空间的设备组合起来,是观察这种分工的教学入口。[1] 这里另构造一个更小的模型,避免先被具体指令集淹没。
把“输入一个数,输出它加一”拆开
约定输入只允许0到254之间的整数,数据寄存器R是8位无符号数,程序只有三条指令:IN R;ADD R,1;OUT R。另有程序计数器PC指出下一条指令,开始为0。
输入41时,第一步把外部输入解释为整数41并放进R,PC变1;第二步计算42并写回R,PC变2;第三步把R的值送到输出端,PC变3并停止。状态可以写成“PC、R、输出记录”的组合。指令不是结果本身,而是规定状态怎样变化的规则。
若输入是字符“41”,输入层还需要先把两个字符解析为整数;若输出设备是屏幕,还要把42转成字符编码并绘制字形。我们为教学把这些工作藏进IN和OUT,因此不能用这个模型声称真实机器一条指令就完成了键盘或屏幕操作。
一段可以亲手核对的状态轨迹
本例的抽象轨迹如下,横线表示尚未写入。它是教学模型的手工推演,不是某种CPU的指令跟踪:
| 执行后 | PC | R | 输出 |
|---|---|---|---|
| 初始 | 0 | — | 空 |
| IN | 1 | 41 | 空 |
| ADD | 2 | 42 | 空 |
| OUT | 3 | 42 | 42 |
读这个表应能回答:数据是什么时候进入的,哪一步改变了数值,哪一步产生了外部可见结果。如果计算完成却没有执行OUT,内部状态已有42,用户仍可能看不到输出。
模型还必须规定错误。我们拒绝255,是因为“加一仍可表示”属于本例输入契约;另一种机器可以选择模256回绕,得到0,但那是另一套语义。不能一边说得到数学整数的加一,一边悄悄按固定位宽截断。
程序怎样让同一台机器做不同的事
把ADD换成别的操作,或加入条件跳转,就能改变执行路径;重复读取、比较与写入,可以组成复杂流程。程序把任务组织成步骤,处理器实现这些步骤的基本机制。程序和数据在许多现代系统中都保存在可寻址存储器里,但取指、执行、缓存与权限有各自规则。
实际CPU会有流水、乱序执行、多级缓存,设备也可能通过DMA搬运数据。上表按顺序描述可观察语义,不声称每条指令恰好占一个时钟周期。顺序模型有助于解释“算什么”,还不足以预测“多久算完”。
更换程序能扩大任务范围,也不能消除资源与可计算性的限制。有限内存装不下任意大的输入;某些任务可能需要不可接受的时间;也不是每个有意义的问题都能由通用算法决定。
给模型加一次分支,程序就能作选择
前面的加一程序只有固定路径。现在构造另一个任务:读入一个0到255之间的整数,若等于0就输出0,否则输出1。为此增加一条条件跳转:R等于0时,把PC改到“输出0”的位置;否则继续到“输出1”的位置。
这时PC不再只是每次加一。输入0与输入41,会让同一份程序走过不同指令;选择由寄存器中的数据与跳转规则共同决定。这足以说明条件执行的基本机制,不需要先把“判断”解释成人的理解或意志。
再加入向前面位置跳转,就能重复一段操作。例如不断读取值并累计,遇到终止标记后输出总和。循环让有限长度的程序描述任意多次重复,但每次运行仍受输入、时间和存储限制。一个没有正确终止条件的循环,也可能一直无法产生用户等待的输出。
这里有三个需要分别检查的对象:程序文本是否包含正确分支,运行时数据使哪条分支被选择,以及输出是否满足任务。只看源代码里出现“if”,并不知道本次运行发生了什么。后续读控制流图时,正是在更明确地记录这些可能路径。
存储器里的地址,与现实世界的对象
把存储器先看成一排编号盒子。地址用于找到某个位置,内容是该位置保存的位串。地址8与其中保存的数8可以碰巧一样,但含义不同;修改盒子里的内容,并不自动改变盒子的编号。
程序可以用多个位置表示一个对象,例如字符数组或一张图。对象边界由格式和程序决定,不是每个地址天然对应一个完整意义。读取四个字节究竟得到一个整数、四个字符还是四个像素,还要看约定。
在这个简化层次里,程序读写地址,CPU执行运算;实际机器还要处理缓存、虚拟地址和权限。虚拟地址不是承诺某个固定物理位置,缓存中的副本也可能让观察更复杂。但这些机制并没有取消“必须说明读写哪个对象”的责任。
一个有帮助的区分是存储容量与运算能力。更大的内存可以保存更多状态,却不直接让每次加法更快;更高的运算吞吐也不保证输入能及时送达。计算是状态变换,存储与输入输出决定这些状态怎样获得、保存和离开。
从用户按键到屏幕结果,谁负责哪一步
以键入“41”并显示“42”为例,可以列出五个层次:键盘事件被接收;输入程序得到字符序列;解析器把十进制文本转换为整数;算术过程加一;输出程序再把整数格式化为字符并交给显示系统。
如果输入是“4l”,末尾是字母l,解析器应拒绝、提示还是截断?如果只接受整数,输入“41.5”又怎样处理?这些选择不是ALU能替用户决定的。算术单元忠实执行它收到的位模式,系统仍可能因为接口处理错误而失败。
程序计数器、寄存器与内存因此提供了机制解释;用户任务还需要数据契约。一个结果可见但错误,和一个结果正确却没有送达,属于不同失败。定位问题时,沿数据经过的阶段逐步检查,比把一切归结为“电脑没算对”更有效。
这也解释了为什么“计算机是输入、处理、输出”虽不错,却还不完整:我们必须进一步说明表示规则、可保存的状态、控制流程与每个边界的责任,才能理解同一设备怎样承载不同软件。
抽象机器怎样帮助我们判断两种实现是否等价
把机器状态记成s=(PC,R,M,I,O),分别代表控制位置、寄存器、内存、剩余输入与已产生输出。每条指令定义一项状态转换;一段程序的运行,就是从初始状态出发形成的转换序列。这个表示比“它算了一下”更明确,因为可以指出哪一项状态发生变化。
两种实现内部轨迹可以完全不同,却在规定输入上产生相同可观察输出。例如一台机器用专用加法器,一台分两次较小运算得到结果。若接口只承诺最终值,内部差异可能不影响功能等价;若接口还承诺每个时刻的输出与响应时间,就需要更强关系。
因此等价总要说“相对于哪些观察”。忽略耗时的数学函数模型不能证明实时响应,忽略异常的模型不能证明失败行为一致。抽象有价值,是因为它主动舍弃一些细节以回答一个问题;当问题变了,就要检查被舍弃的细节是否重新重要。
本例的顺序状态机可以解释指令与数据怎样协作,但没有多线程、缓存一致性和设备并发。它为后续系统学习建立语言,不能靠一个小模型承诺现代计算机所有行为都如此简单。
输入输出决定计算怎样成为一个系统
传感器把温度等物理量转换成电信号和数字读数;网卡按协议收发数据;显示器把像素数据转成光。每个环节都可能有延迟、精度、失败和权限限制。程序算出一个数,与设备真正执行对应动作是不同事件。
所以,“程序没有报错”并不等于结果满足用户需要。输入是否按约定解析、溢出是否被处理、输出是否送达,都是完整行为的一部分。若输入设备把41读成14,处理器忠实完成加一,系统仍然给错了用户想要的答案。
这也是本篇的边界:它解释表示、状态变换与外部行为之间的关系,不提供真实硬件性能结论。下一篇继续追问:这些数、文字和图像,为什么可以共同建立在0与1之上?
来源、证据与阅读路线
- Nand2Tetris,The Elements of Computing Systems,计算机体系结构与完整软硬件教学项目
- Unicode FAQ:UTF-8等编码形式,用于区分字符、编码和底层数据
证据范围:抽象机器的状态轨迹和8位整数边界已按所定规则核对;这是教学模型,不是对真实处理器、操作系统或通用计算能力的实验验证。
写下你的评论
要发表评论,您必须先登录。