计算机是如何识别代码的
这个问题要展开的话蛮复杂的,展开到几万几十万字都可以。但针对题主的问题可以有个更具体精确的回答方式。因为我也不是 CS 科班出身,也才大二,就几个关键的节点说一下我的理解。可能有些错漏,权当小导论看看得了,有错误敬请指出。
大致过程
我的理解中,整个过程大致可以分为两段:
- 代码到机器码的过程
- 机器码被 CPU 执行
其中阶段 1 是编译原理的领域,阶段 2 是计组的领域,1 和 2 中间应该是操作系统的领域。
举个简单的例子,然后让过程再具体一点的话(依旧忽略一些不那么关键的步骤),可能是:
编译阶段:把高级语言变成机器语言(按题主的问题,其实可能更需要这方面的信息)
- 分析高级语言的代码并预处理
- 进行编译,把高级语言代码变成汇编语言代码
- 进行汇编,把汇编语言代码变成机器语言,生成可执行文件(二进制形式)
- (链接阶段可以在编译阶段进行但具体时机不固定,因不涉及关键所以略)
系统阶段:加载程序、调度进程、管理内存等(对不起原谅我系统还没怎么学)
- 读取可执行文件
- (省略一些非关键部分)
- 把程序从硬盘加载到内存中
处理器阶段:执行二进制程序
- 从内存读取程序的指令
- 指令通过数据通路
在流水线的每个阶段进行不同的操作
- 把机器码指令拆分出不同部分
- 根据指令执行不同的操作(读取、写入、计算等)
其中系统阶段可能比较倾向于中介的成分,不是特别特别关键。因为这个问题我前阵子也在思考,实际上就算对系统一无所知(我就是),光靠知道“编译阶段得到机器码程序,然后经过一些过程把机器码加载到指令内存中”,就足够把编译部分和执行部分串联起来了。
就我而言,在这个问题中,以前卡住的最关键的点是:文本到信号的过程,也就是把机器码加载到指令内存的过程。这部分只要学一下加载器应该就可以彻底贯通了。
补充:计组部分的具体过程
上学期刚学完 MIPS,这里就用 MIPS 举例了。
基础概念
- 寄存器:操作起来最快最便捷的核心存储单元,数量相当有限(比如 MIPS 有 32 个通用寄存器)
- 内存(RAM):操作起来要多废时间读写的存储空间,量大管饱性能差(相比寄存器而言)
处理器结构 & 数据通路
用计组黑皮书的一张图举例(图 4.11)。这个通路叫 Datapath(数据通路),数据从左边流向右边,在通路的不同部分进行各种操作。
关键元件解释(从左到右,忽略上方的次要部分):
- PC 寄存器:指向指令内存中的指令的指针,用于决定要执行指令(指哪取哪)
- 指令内存:专门存储程序指令的内存空间
- 寄存器:通用寄存器
- ALU:算术逻辑单元,用于执行运算
- 数据内存:专门用于存储数据的内存空间
- 蓝色的标签:控制单元连接过来的信号
- 数据单元:根据当前运行的指令,控制各个部分的部分参数(类似于全局辅助参数的集合)
每次执行一条指令,先从 PC 寄存器得到要执行的指令的地址,把指定取出然后译码,拆出不同的部分。比如一条 MIPS 的 R 类指令的二进制存储结构是这样的:
对于不同指令的结构问题,可以去看别的文章,这里不赘述,比如可以看看这篇文章。
R 型指令包括了基本的加减乘除等计算指令,我们这里用加法举例。
比如 add $t0, $t1, $t2 # $t0 = $t1 + $t2 这条指令的作用是把寄存器 t1, t2 的值加起来,存入 t0。寄存器对应的编号是:
$t0→8(二进制01000)$t1→9(二进制01001)$t2→10(二进制01010)
所以结构是这样的:
| Field | opcode | rs ($t1) | rt ($t2) | rd ($t0) | shamt | funct |
|---|---|---|---|---|---|---|
| Binary | 000000 | 01001 | 01010 | 01000 | 00000 | 100000 |
拼起来就得到:000000 01001 01010 01000 00000 100000,这就是上面这条汇编指令在指令内存中的形式。
这条命令被取出来,然后译码阶段把这条指令再次拆开,拆出不同的部分,然后根据 opcode 和 funct 可以知道这条指令是 R 型指令的加法指令,于是在 ALU 里的行为就可以知道了。
可以从图上看到 ALU 的结果进入了一个 MUX,也就是多路复用器,另一个输入是来自数据内存的,这个 MUX 代表:这条指令可能需要把 ALU 的计算结果存到寄存器(比如 R 型指令),或者要从内存里读取数据到寄存器(比如 lw 指令),要根据情况,把对应的值存到寄存器里。
话说回来,对于 ADD 指令,计算结果就会通过 ALU 的出口,到 MUX,再到寄存器,这条指令就结束了。
对于 PC 连着的上半图的部分呢,其实是用于决定 PC 指针的移动的。右上角的 MUX 的输出是连着 PC 的输入的,它会从两个值里取其一并输入给 PC 寄存器。这两个输入代表着两种情况:
- 程序顺序运行,PC 自然移动到下一条指令,则
PC:=PC+4。因为一条指令长 32 位,所以是 4 字节,按字节寻址,所以每次加 4 字节移动到下一条指令。 - 程序遇到分支,这时第二个输入对应分支要跳转的位置,赋值给 PC 就可以实现程序的跳转。
其它
本来还有很多东西,比如流水线、冒险等等,这里不详细展开了,离主线比较远。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。