第2集:计算机是如何执行程序的?
学习目标
通过本集的学习,你将能够:
- 理解从高级语言到机器语言的完整转换过程
- 描述 CPU 的基本工作原理
- 区分内存和寄存器的作用
- 读懂简单的机器语言和汇编代码
2.1 从高级语言到机器语言
在上一集,我们知道编译器把高级语言翻译成机器语言。让我们更详细地看看这个过程:
┌─────────────────────────────────────────────────────┐
│ 第1层:高级语言 (C、Java、Python) │
│ int a = 1 + 2; │
└─────────────────────┬─────────────────────────────┘
│ 编译器
▼
┌─────────────────────────────────────────────────────┐
│ 第2层:汇编语言 (Assembly) │
│ mov $1, %eax │
│ add $2, %eax │
│ mov %eax, a │
└─────────────────────┬─────────────────────────────┘
│ 汇编器
▼
┌─────────────────────────────────────────────────────┐
│ 第3层:机器语言 (Machine Code) │
│ 01011001 00000001 00000000 00000000 │
│ 00000101 00000010 00000000 00000000 │
│ 10001001 00000000 01000000 00000000 │
└─────────────────────────────────────────────────────┘注意:实际上,现代编译器通常直接从高级语言生成机器码,汇编语言只是一个中间表示,方便人类阅读。
2.2 CPU 是如何工作的?
CPU(中央处理器)是计算机的"大脑",它的工作可以用一个简单的循环来描述:取指-译码-执行(Fetch-Decode-Execute)。
让我们用工厂流水线来类比:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 取指 │ → │ 译码 │ → │ 执行 │
└─────────┘ └─────────┘ └─────────┘
↑ ↓ ↓
从内存 理解指令 执行指令
读取指令 做什么 完成工作详细步骤:
- 取指(Fetch):CPU 从内存中取出下一条要执行的指令
- 译码(Decode):CPU 弄明白这条指令要做什么
- 执行(Execute):CPU 执行这条指令
- 更新:更新程序计数器,准备取下一条指令
- 重复:回到步骤1,继续执行下一条
这个循环每秒可以执行数百万次甚至数十亿次!
2.3 内存、寄存器与指令
让我们认识计算机中的几个关键组件:
内存(Memory)
- 特点:容量大,速度相对较慢
- 作用:存储程序和数据
- 类比:像一个大仓库,可以放很多东西,但找东西需要时间
寄存器(Register)
- 特点:容量极小,速度极快
- 作用:CPU 执行计算时临时存放数据
- 类比:像 CPU 手上的小记事本,随手就能拿到
指令(Instruction)
- 特点:告诉 CPU 做什么的命令
- 组成:操作码 + 操作数
- 例子:加法、加载数据、存储数据
让我们用图来展示它们的关系:
┌─────────────────────────────────────────────────┐
│ 内存 │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │数据A │ │数据B │ │指令1 │ │指令2 │ ...│
│ └──────┘ └──────┘ └──────┘ └──────┘ │
└────────────────────┬────────────────────────┘
│
┌───────────┴───────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ 取指/存数 │ │ CPU │
│ (加载/存储) │ │ ┌────────────┐ │
└──────────────────┘ │ │ 寄存器 │ │
▲ │ │ %rax %rbx │ │
│ │ │ %rcx %rdx │ │
└──────────────┤ └────────────┘ │
│ ┌────────────┐ │
│ │ ALU(运算) │ │
│ └────────────┘ │
└──────────────────┘2.4 简单的机器语言示例
让我们看一个非常简单的例子:计算 1 + 2,然后把结果存起来。
高级语言版本
int result = 1 + 2;汇编语言版本(x86-64)
mov $1, %rax ; 把数字 1 放到寄存器 rax 中
add $2, %rax ; 把数字 2 加到 rax 中(现在 rax = 3)
mov %rax, result ; 把 rax 的值存到 result 变量中机器语言版本(简化示意)
10111000 00000001 00000000 00000000 00000000 ; mov $1, %rax
00000101 00000010 00000000 00000000 00000000 ; add $2, %rax
10001001 00000101 00000000 01000000 00000000 ; mov %rax, result虽然机器语言都是 0 和 1,但 CPU 能完美理解它们!
2.5 一个完整的执行流程
让我们跟踪 1 + 2 这个简单计算的完整执行过程:
初始状态:
内存中有指令和数据
寄存器全是 0
程序计数器(PC)指向第一条指令
步骤1:取指
CPU 从 PC 指向的位置取出指令:"mov $1, %rax"
PC 自动指向下一条指令
步骤2:译码
CPU 理解:这是一条"移动"指令
源操作数是 1
目标是寄存器 rax
步骤3:执行
CPU 把数字 1 放到 rax 寄存器中
现在 rax = 1
步骤4:取指(第二条指令)
CPU 取出:"add $2, %rax"
PC 继续前进
步骤5:译码
这是一条"加法"指令
要把 2 加到 rax 上
步骤6:执行
rax = rax + 2 = 1 + 2 = 3
现在 rax = 3
步骤7:取指(第三条指令)
取出:"mov %rax, result"
步骤8:译码
把 rax 的值存到内存的 result 位置
步骤9:执行
把 3 从 rax 存到内存中的 result 变量
完成!result = 32.6 常见的指令类型
虽然不同的 CPU 架构有不同的指令集,但基本的指令类型都差不多:
| 指令类型 | 作用 | 例子 |
|---|---|---|
| 数据传输 | 在寄存器和内存间移动数据 | mov, load, store |
| 算术运算 | 加减乘除等运算 | add, sub, mul, div |
| 逻辑运算 | 与、或、非、异或等 | and, or, not, xor |
| 比较 | 比较两个值 | cmp |
| 跳转 | 改变执行顺序 | jmp, je, jne |
| 函数调用 | 调用函数 | call, ret |
2.7 自测一下
问题 1
CPU 执行指令的基本循环是?
A) 读-写-存
B) 取指-译码-执行
C) 输入-处理-输出
D) 编译-汇编-链接
问题 2
关于寄存器和内存,以下说法正确的是?
A) 寄存器容量大,速度快
B) 内存容量小,速度快
C) 寄存器容量小,速度快
D) 内存和寄存器一样快
问题 3
什么是机器语言?
问题 4
请描述"取指-译码-执行"循环的三个步骤。
答案:
- B
- C
- 机器语言是由 0 和 1 组成的、CPU 可以直接执行的语言
- 取指:从内存读取指令;译码:理解指令要做什么;执行:完成指令的工作
2.8 下集预告
下一集,我们将探索:编程语言家族树!
我们会了解:
- 机器语言、汇编语言、高级语言的区别
- 编译型语言 vs 解释型语言
- C、Java、Python 各有什么特点
- 语言的抽象层次是如何发展的
准备好了吗?我们下集见!
参考资料
- 《计算机组成与设计:硬件/软件接口》
- 《深入理解计算机系统》
- x86 汇编语言教程