跳转至

01-计算机是如何工作的

重要性:⭐⭐⭐⭐⭐ 实用度:⭐⭐⭐⭐⭐ 学习时间:2-3天 必须掌握:是


为什么学这一章?

在深入代码如何变成可执行程序之前,你需要先理解计算机的基本工作原理。这一章将为你建立计算机系统的全局认知,为后续学习打下坚实基础。

学完这一章,你将能够: - ✅ 解释计算机的五大基本组成部分 - ✅ 理解冯·诺依曼架构的核心思想 - ✅ 明白硬件和软件如何协同工作 - ✅ 建立计算机系统的层次化思维


📖 核心概念

1. 计算机的五大组成部分

计算机本质上是一个信息处理系统,由五个基本部分组成:

Text Only
┌─────────────────────────────────────────────────────────────┐
│                      计算机系统架构                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   输入设备        输出设备                                    │
│   (键盘/鼠标)     (显示器/打印机)                            │
│       ↓              ↑                                     │
│       └────────┬─────┘                                     │
│                ↓                                           │
│   ┌─────────────────────────┐                              │
│   │        运算器 (ALU)      │  ← 执行算术和逻辑运算        │
│   ├─────────────────────────┤                              │
│   │        控制器            │  ← 指挥协调各部件工作        │
│   ├─────────────────────────┤                              │
│   │        存储器            │  ← 存储程序和数据            │
│   │   (内存 + 外存)          │                              │
│   └─────────────────────────┘                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

运算器(ALU - Arithmetic Logic Unit)

是什么:计算机的"计算器"

功能: - 算术运算:加、减、乘、除 - 逻辑运算:与、或、非、异或 - 比较运算:等于、大于、小于

生活化类比

就像你做题时的草稿纸和笔,ALU负责所有的计算工作。

代码示例

C++
int a = 5, b = 3;
int c = a + b;    // ALU执行加法
int d = a & b;    // ALU执行按位与
bool e = a > b;   // ALU执行比较

控制器(Control Unit)

是什么:计算机的"指挥官"

功能: - 从内存中取指令 - 解码指令 - 控制其他部件执行指令 - 协调各部件的工作节奏

生活化类比

就像乐队的指挥,不直接演奏乐器,但指挥着整个乐队的节奏和配合。

存储器(Memory)

是什么:计算机的"记忆系统"

分类

Text Only
存储器层次结构(速度 vs 容量)

速度  ↑    ┌─────────────┐
快    │    │   寄存器     │  ← CPU内部,最快,容量最小(几十字节)
      │    ├─────────────┤
      │    │   缓存       │  ← L1/L2/L3,快,容量小(KB到MB)
      │    ├─────────────┤
      │    │   内存(RAM)  │  ← 主存,较快,容量中等(GB级)
      │    ├─────────────┤
      │    │   硬盘/SSD   │  ← 外存,慢,容量大(TB级)
慢    │    ├─────────────┤
      │    │   网络存储   │  ← 最慢,容量最大
      └    └─────────────┘

           容量 →
           小                    大

为什么需要层次结构? - 速度快的存储器贵且容量小 - 速度慢的存储器便宜且容量大 - 通过层次结构平衡性能和成本

输入设备

是什么:向计算机输入数据和指令的设备

常见设备: - 键盘、鼠标 - 麦克风、摄像头 - 触摸屏 - 传感器(温度、光线等)

输出设备

是什么:将计算机处理结果呈现给用户的设备

常见设备: - 显示器 - 打印机 - 扬声器 - 投影仪


2. 冯·诺依曼架构

什么是冯·诺依曼架构?

1945年,数学家冯·诺依曼提出了一种计算机设计思想,成为现代计算机的基础。

核心思想(三大原则)

Text Only
┌─────────────────────────────────────────────────────────────┐
│                  冯·诺依曼架构三大原则                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1️⃣ 存储程序原则                                             │
│     程序和数据以同等地位存储在存储器中                        │
│     计算机可以从存储器中读取指令并执行                        │
│                                                             │
│  2️⃣ 二进制原则                                               │
│     指令和数据都用二进制表示                                  │
│     0和1可以表示所有信息                                      │
│                                                             │
│  3️⃣ 五大部件原则                                             │
│     计算机由运算器、控制器、存储器、输入设备、输出设备组成      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

存储程序原则的意义

革命性变化

在冯·诺依曼架构之前: - 计算机程序通过物理线路(插线板)设置 - 改变程序需要重新接线 - 每台计算机只能做特定任务

在冯·诺依曼架构之后: - 程序存储在内存中 - 改变程序只需要改变内存中的指令 - 同一台计算机可以执行不同任务

生活化类比

就像乐谱和乐器的关系。以前每个乐器只能演奏固定的曲子(硬连线),现在乐器可以演奏任何乐谱(存储程序)。

二进制原则

为什么用二进制?

Text Only
┌─────────────────────────────────────────────────────────────┐
│                   为什么计算机用二进制?                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  物理实现简单                                                │
│  ├── 电路:通电=1,断电=0                                    │
│  ├── 磁盘:磁化=1,未磁化=0                                  │
│  ├── 光盘:凹坑=1,平面=0                                    │
│  └── 光纤:有光=1,无光=0                                    │
│                                                             │
│  可靠性高                                                    │
│  ├── 只有两种状态,容易区分                                  │
│  └── 抗干扰能力强                                            │
│                                                             │
│  运算简单                                                    │
│  ├── 加法:0+0=0, 0+1=1, 1+0=1, 1+1=10                      │
│  └── 逻辑运算:与、或、非都可以用电路实现                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二进制与十进制的转换

Text Only
十进制 13 = 二进制 1101

计算过程:
13 ÷ 2 = 6 余 1
 6 ÷ 2 = 3 余 0
 3 ÷ 2 = 1 余 1
 1 ÷ 2 = 0 余 1

从下往上读:1101

验证:1×2³ + 1×2² + 0×2¹ + 1×2⁰ = 8 + 4 + 0 + 1 = 13 ✓

3. 计算机系统的层次结构

计算机系统是一个复杂的层次结构,从硬件到软件,每一层都建立在下一层之上:

Text Only
┌─────────────────────────────────────────────────────────────┐
│  第6层:应用程序层                                            │
│  ├── 浏览器、Word、游戏、AI训练程序                           │
│  └── 用户直接使用的软件                                       │
├─────────────────────────────────────────────────────────────┤
│  第5层:高级语言层                                            │
│  ├── C/C++、Python、Java、JavaScript                         │
│  └── 程序员编写的源代码                                       │
├─────────────────────────────────────────────────────────────┤
│  第4层:汇编语言层                                            │
│  ├── x86-64汇编、ARM汇编                                      │
│  └── 人类可读的机器指令                                       │
├─────────────────────────────────────────────────────────────┤
│  第3层:操作系统层                                            │
│  ├── Windows、Linux、macOS                                    │
│  └── 管理硬件资源,提供服务                                   │
├─────────────────────────────────────────────────────────────┤
│  第2层:指令集架构层(ISA)                                    │
│  ├── x86-64、ARM、RISC-V                                      │
│  └── 硬件和软件的接口                                         │
├─────────────────────────────────────────────────────────────┤
│  第1层:硬件层                                                │
│  ├── CPU、内存、硬盘、显卡                                    │
│  └── 物理硬件设备                                             │
└─────────────────────────────────────────────────────────────┘

各层之间的关系

Text Only
用户
  ↓ 使用
应用程序
  ↓ 调用
高级语言代码 (C++/Python)
  ↓ 编译/解释
汇编代码 / 机器码
  ↓ 系统调用
操作系统
  ↓ 指令
硬件 (CPU/内存/IO)

为什么需要分层?

好处: 1. 抽象复杂:上层不需要了解下层的细节 2. 易于开发:程序员专注于当前层 3. 便于维护:修改一层不影响其他层 4. 促进标准化:层与层之间定义清晰的接口

生活化类比

就像快递系统: - 你(用户)只需要填写快递单 - 快递员(应用程序)负责取件和派送 - 分拣中心(操作系统)负责路由规划 - 货车(硬件)负责实际运输

你不需要知道货车走哪条路,快递员不需要知道货车怎么开。


4. 指令执行的基本周期

CPU执行程序的基本过程:

Text Only
┌─────────────────────────────────────────────────────────────┐
│                    指令执行周期(取指-执行循环)                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│     ┌──────────┐                                            │
│     │   开始    │                                            │
│     └────┬─────┘                                            │
│          ↓                                                  │
│     ┌──────────┐     ┌─────────────────────────────┐       │
│     │  取指     │────→│ 从内存中读取下一条指令       │       │
│     │ (Fetch)  │     │ PC(程序计数器)指向下一条    │       │
│     └────┬─────┘     └─────────────────────────────┘       │
│          ↓                                                  │
│     ┌──────────┐     ┌─────────────────────────────┐       │
│     │  译码     │────→│ 解析指令的操作类型和操作数   │       │
│     │ (Decode) │     │ 确定要执行什么操作           │       │
│     └────┬─────┘     └─────────────────────────────┘       │
│          ↓                                                  │
│     ┌──────────┐     ┌─────────────────────────────┐       │
│     │  执行     │────→│ ALU执行运算,或访问内存      │       │
│     │(Execute) │     │ 或进行IO操作                 │       │
│     └────┬─────┘     └─────────────────────────────┘       │
│          ↓                                                  │
│     ┌──────────┐     ┌─────────────────────────────┐       │
│     │  访存     │────→│ 如有需要,读写内存数据       │       │
│     │(Memory)  │     │ (非所有指令都需要)          │       │
│     └────┬─────┘     └─────────────────────────────┘       │
│          ↓                                                  │
│     ┌──────────┐     ┌─────────────────────────────┐       │
│     │  写回     │────→│ 将结果写回寄存器或内存       │       │
│     │(Writeback)│    │ 更新程序状态                 │       │
│     └────┬─────┘     └─────────────────────────────┘       │
│          ↓                                                  │
│     ┌──────────┐                                            │
│     │  重复     │────────────────────────────────────────    │
│     └──────────┘            (除非遇到停机指令)              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

具体例子:执行 a = b + c

假设: - 变量a在内存地址1000 - 变量b在内存地址1004 - 变量c在内存地址1008

Text Only
执行过程:

1. 取指:从内存读取 "LOAD b" 指令
2. 译码:解析为"从地址1004加载数据到寄存器R1"
3. 执行:从内存地址1004读取b的值到R1

4. 取指:从内存读取 "LOAD c" 指令
5. 译码:解析为"从地址1008加载数据到寄存器R2"
6. 执行:从内存地址1008读取c的值到R2

7. 取指:从内存读取 "ADD R1, R2" 指令
8. 译码:解析为"将R1和R2相加,结果存到R3"
9. 执行:ALU执行加法,R3 = R1 + R2

10. 取指:从内存读取 "STORE a" 指令
11. 译码:解析为"将R3的值存储到地址1000"
12. 执行:将R3的值写入内存地址1000

需要多少时钟周期? - 现代CPU可以在1个周期内执行多条指令(流水线、超标量) - 但基本思想仍然是取指-译码-执行


🧪 动手实验

实验1:观察程序的执行

目的:理解程序是如何被CPU执行的

步骤

  1. 创建一个简单的C程序

    C++
    // hello.c
    #include <stdio.h>  // 引入头文件
    
    int main() {
        int a = 5;
        int b = 3;
        int c = a + b;
        printf("%d + %d = %d\n", a, b, c);
        return 0;
    }
    

  2. 编译并运行

    Bash
    gcc -o hello hello.c
    ./hello
    

  3. 使用GDB单步执行

    Bash
    gcc -g -o hello hello.c
    gdb ./hello
    
    (gdb) break main
    (gdb) run
    (gdb) next    # 单步执行,观察每一步
    (gdb) print a # 查看变量值
    (gdb) info registers  # 查看寄存器
    

思考问题: - 每执行一行C代码,实际执行了多少条机器指令? - 变量a、b、c存储在哪里(寄存器还是内存)?

实验2:观察汇编代码

目的:理解C代码如何转换成机器指令

步骤

  1. 生成汇编代码

    Bash
    gcc -S -O0 hello.c  # 生成hello.s
    

  2. 查看汇编代码

    Bash
    cat hello.s
    

  3. 对比C代码和汇编代码

  4. 找到变量a、b、c的初始化
  5. 找到加法运算对应的汇编指令
  6. 找到printf调用

思考问题: - 一条C语句对应多少条汇编指令? - 哪些C语句生成的汇编代码最多?

实验3:使用Compiler Explorer

目的:可视化编译过程

步骤

  1. 打开 https://godbolt.org/

  2. 左侧输入代码:

    C++
    int add(int a, int b) {
        return a + b;
    }
    

  3. 右侧查看生成的汇编代码

  4. 尝试不同的编译器优化级别:

  5. 在编译器选项中添加 -O0(无优化)
  6. 改为 -O3(最高优化)
  7. 观察汇编代码的变化

思考问题: - 优化后的代码有什么变化? - 为什么简单的加法函数在优化后可能完全不同?


💡 核心要点总结

必须记住的概念

  1. 计算机五大部件:运算器、控制器、存储器、输入设备、输出设备
  2. 冯·诺依曼三大原则:存储程序、二进制、五大部件
  3. 存储程序的意义:程序和数据同等存储,计算机变得通用
  4. 系统层次结构:从硬件到应用,层层抽象
  5. 指令执行周期:取指→译码→执行→访存→写回

关键理解

Text Only
程序执行的本质:

源代码
  ↓ 编译/解释
机器指令序列
  ↓ CPU执行
┌─────────────────────────────────────┐
│  取指 → 译码 → 执行 → 访存 → 写回   │
│    ↑________________________________│
│              循环执行                │
└─────────────────────────────────────┘
程序运行结果

❓ 常见问题

Q1:为什么计算机只能理解二进制?

A:因为计算机硬件基于电子电路,只有两种稳定状态(通电/断电)。用二进制表示正好对应这两种状态,物理实现简单可靠。

Q2:高级语言(如Python)需要编译吗?

A:Python是解释型语言,不需要显式编译,但执行时仍然需要转换成机器码。Python解释器会逐行读取代码,转换成字节码,然后在虚拟机上执行。

Q3:CPU的时钟频率(如3.5GHz)是什么意思?

A:表示CPU每秒可以执行35亿个时钟周期。每个时钟周期可以执行一条或多条指令。频率越高,理论上CPU执行速度越快。

Q4:为什么需要操作系统?程序不能直接运行在硬件上吗?

A:理论上可以(裸机编程),但操作系统提供了: - 硬件抽象:程序不需要知道硬件细节 - 资源管理:多个程序共享CPU、内存 - 安全保护:防止程序破坏系统或其他程序 - 便捷服务:文件系统、网络、图形界面等


📚 扩展阅读

  1. 《编码:隐匿在计算机软硬件背后的语言》 - Charles Petzold
  2. 从摩斯码到计算机,循序渐进理解计算机原理

  3. 《计算机系统要素》 - Noam Nisan等

  4. 从逻辑门到操作系统,动手构建计算机系统

  5. 在线资源

  6. Nand2Tetris(从与非门到俄罗斯方块)
  7. Ben Eater的计算机原理视频(YouTube/B站)

🎯 下一步

完成本章后,你已经建立了计算机系统的全局认知。接下来进入 02-从代码到执行的旅程,了解代码从编写到运行的完整过程。