Skip to content Skip to footer

总线 -- 计算机内部的「公路系统」!

总线 -- 计算机内部的「公路系统」

计算机内部各个组件之间需要不停地交换信息。CPU 要从内存取指令、要读写数据,这些信息怎么传输?答案是——总线(Bus)。

总线是连接 CPU、内存、硬盘、显卡等设备的一组公共通信线路,就像城市里的公路系统——有主干道、有支路,每条路都有明确的用途。

本讲将带你理解三种总线的分工,并用 Python 模拟一次完整的内存读操作。

公路系统类比:三种路,三种用途

想象你在一个大城市里送快递。快递员需要知道三件事:送到哪、送什么东西、收件人要不要签收。

计算机内部也有完全类似的三种「公路」:

总线类型功能传输方向生活类比

地址总线(Address Bus)传输要访问的内存地址CPU → 内存(单向)快递单上的「门牌号」——告诉收件方「我要找这个地址」

数据总线(Data Bus)传输实际的数据内容双向快递员手里的「包裹」——东西本身,可以送出也可以接收

控制总线(Control Bus)传输读写、中断等控制信号CPU ↔ 各部件(双向)「签收单」——告诉对方现在是取件还是派件,以及传送是否完成

地址总线的宽度决定了 CPU 能访问多大的内存。32 位地址总线可以访问 2^32 = 4GB 内存空间,64 位地址总线理论上可以访问 2^64 字节(约 16EB),这就是为什么 32 位操作系统最多只能用 4GB 内存。

一次内存读操作的完整流程

假设 CPU 需要从内存地址 100 读取一个数据。整个过程分 4 步,三条总线协同工作:

步骤拆解

CPU 放地址:CPU 将地址值 100 放到地址总线上。内存此时收到了门牌号。

CPU 发信号:CPU 在控制总线上发出「读」信号(READ)。内存知道对方是要取东西。

内存放数据:内存收到地址和读信号后,找到地址 100 处存储的数据,把它放到数据总线上。

CPU 收数据:CPU 从数据总线上读取数据,存入内部寄存器。传输完成。

时序图

下面用 ASCII 时序图展示这条指令执行时的总线状态变化:

时间 | 地址总线 控制总线 数据总线

------|------------------------------------------

T0 | 100 IDLE XXXX ← CPU 放上地址

T1 | 100 READ XXXX ← CPU 发出读信号

T2 | 100 READ 0x2A ← 内存放上数据

T3 | XXXX IDLE 0x2A ← CPU 取走数据

------|------------------------------------------

结果: CPU 从地址 100 读到了数据 0x2A

代码演示:用 Python 模拟总线交互过程

下面这段 Python 代码完整模拟了一次内存读操作中,地址总线、数据总线、控制总线的协作过程。

实例

"""

总线交互模拟 - 一次完整的内存读操作(runoob 演示)

本程序模拟了 CPU 通过三种总线从内存读取数据的完整过程:

1. 地址总线: CPU 发出要访问的内存地址

2. 控制总线: CPU 发出读/写控制信号

3. 数据总线: 内存返回/接收数据

每次总线状态变化都打印详细的日志,方便观察三总线的协同工作。

"""

import time

class AddressBus:

"""地址总线 - 单向:CPU 将地址发送给内存"""

def __init__(self):

self.value = None # 当前总线上的地址值

self.width = 16 # 16 位地址总线,可寻址 64KB

def set_address(self, addr):

"""CPU 将地址放到地址总线上"""

self.value = addr

print(f" [地址总线] CPU 放入地址: {addr} (0x{addr:04X})")

return addr

def clear(self):

"""清除总线"""

self.value = None

print(f" [地址总线] 总线清空,进入空闲状态")

class DataBus:

"""数据总线 - 双向:CPU 和内存都可以收发数据"""

def __init__(self):

self.value = None # 当前总线上的数据值

self.width = 8 # 8 位数据总线,每次传一个字节

self.direction = None # 'TO_CPU' 或 'TO_MEMORY'

def send_from_cpu(self, data):

"""CPU 发送数据到总线"""

self.value = data

self.direction = 'TO_MEMORY'

print(f" [数据总线] CPU → 总线, 数据: {data} (0x{data:02X})")

return data

def send_from_memory(self, data):

"""内存发送数据到总线"""

self.value = data

self.direction = 'TO_CPU'

print(f" [数据总线] 内存 → 总线, 数据: {data} (0x{data:02X})")

return data

def read_to_cpu(self):

"""CPU 从数据总线读取数据"""

data = self.value

print(f" [数据总线] CPU 从总线读取数据: {data} (0x{data:02X})")

self.clear()

return data

def read_to_memory(self):

"""内存从数据总线读取数据(写操作时使用)"""

data = self.value

print(f" [数据总线] 内存从总线读取数据: {data} (0x{data:02X})")

self.clear()

return data

def clear(self):

"""清除总线"""

self.value = None

self.direction = None

print(f" [数据总线] 总线清空,进入空闲状态")

class ControlBus:

"""控制总线 - 双向: 传输读写信号、中断信号等"""

# 控制信号类型

READ = 'MEM_READ' # 内存读信号

WRITE = 'MEM_WRITE' # 内存写信号

IDLE = 'IDLE' # 空闲

def __init__(self):

self.signal = self.IDLE

def set_signal(self, signal):

"""设置控制信号"""

self.signal = signal

signal_name = {

self.READ: '内存读 (MEM_READ)',

self.WRITE: '内存写 (MEM_WRITE)',

self.IDLE: '空闲 (IDLE)'

}

print(f" [控制总线] CPU 发出信号: {signal_name.get(signal, signal)}")

def clear(self):

"""清除信号"""

self.signal = self.IDLE

print(f" [控制总线] 信号清空,进入空闲状态")

class SimpleMemory:

"""简化的内存模块 - 256 个字节的存储空间"""

def __init__(self, size=256):

self.size = size

self.data = {}

# 初始化一些演示数据

for i in range(size):

self.data[i] = i % 256 # 地址 i 存储值 i % 256

# 在特定地址放一些特殊值作为演示

self.data[100] = 42 # 地址 100: 存入 42 (0x2A)

self.data[200] = 0xAB # 地址 200: 存入 0xAB (171)

self.data[50] = ord('R') # 地址 50: 存入字母 'R' 的 ASCII 码

self.data[51] = ord('U')

self.data[52] = ord('N')

self.data[53] = ord('O')

self.data[54] = ord('O')

self.data[55] = ord('B')

def read(self, addr):

"""从指定地址读取数据"""

if 0 <= addr < self.size:

return self.data.get(addr, 0)

else:

raise ValueError(f"地址 {addr} 超出内存范围 (0-{self.size-1})")

def write(self, addr, data):

"""向指定地址写入数据"""

if 0 <= addr < self.size:

self.data[addr] = data

else:

raise ValueError(f"地址 {addr} 超出内存范围 (0-{self.size-1})")

class CPU:

"""简化的 CPU - 通过总线与内存交互"""

def __init__(self, addr_bus, data_bus, ctrl_bus, memory):

self.addr_bus = addr_bus # 地址总线

self.data_bus = data_bus # 数据总线

self.ctrl_bus = ctrl_bus # 控制总线

self.memory = memory # 内存

# CPU 内部寄存器

self.mdr = 0 # 数据寄存器(Memory Data Register)

self.mar = 0 # 地址寄存器(Memory Address Register)

def memory_read(self, addr):

"""

执行一次完整的内存读操作

流程:

T0: MAR ← addr (内部准备)

T1: 地址总线 ← addr, 控制总线 ← READ

T2: 内存读取数据, 数据总线 ← data

T3: MDR ← 数据总线, 各总线清空

返回: 读取到的数据值

"""

print(f"\n{'='*55}")

print(f"操作开始: CPU 从内存地址 {addr} 读取数据")

print(f"{'='*55}")

# T0: CPU 内部准备——把地址装入 MAR

self.mar = addr

print(f"\n[T0 - CPU 内部准备]")

print(f" [CPU 内部] MAR (内存地址寄存器) ← {addr}")

# T1: CPU 将地址放到地址总线,同时在控制总线上发出读信号

print(f"\n[T1 - 发出地址和读信号]")

self.addr_bus.set_address(addr)

self.ctrl_bus.set_signal(ControlBus.READ)

# 模拟信号传输延迟

time.sleep(0.001)

# T2: 内存响应——根据地址总线上的地址找到数据,放到数据总线上

print(f"\n[T2 - 内存响应]")

data = self.memory.read(addr)

self.data_bus.send_from_memory(data)

# 模拟信号传输延迟

time.sleep(0.001)

# T3: CPU 从数据总线读取数据,存入 MDR。各总线清空

print(f"\n[T3 - CPU 接收数据]")

self.mdr = self.data_bus.read_to_cpu()

self.ctrl_bus.clear()

self.addr_bus.clear()

# 输出结果

print(f"\n{'='*55}")

print(f"操作完成: 从地址 {addr} (0x{addr:04X}) 读取到数据 {self.mdr} (0x{self.mdr:02X})")

print(f"{'='*55}")

return self.mdr

def memory_write(self, addr, data):

"""

执行一次完整的内存写操作

流程:

T0: MAR ← addr, MDR ← data

T1: 地址总线 ← addr, 数据总线 ← data, 控制总线 ← WRITE

T2: 内存从数据总线取数据,写入地址 addr

T3: 各总线清空

"""

print(f"\n{'='*55}")

print(f"操作开始: CPU 向内存地址 {addr} 写入数据 {data}")

print(f"{'='*55}")

# T0: CPU 内部准备

self.mar = addr

self.mdr = data

print(f"\n[T0 - CPU 内部准备]")

print(f" [CPU 内部] MAR ← {addr}, MDR ← {data}")

# T1: CPU 将地址和数据放到总线上,发出写信号

print(f"\n[T1 - 发出地址、数据和写信号]")

self.addr_bus.set_address(addr)

self.data_bus.send_from_cpu(data)

self.ctrl_bus.set_signal(ControlBus.WRITE)

time.sleep(0.001)

# T2: 内存从数据总线取数据并写入

print(f"\n[T2 - 内存写入]")

received = self.data_bus.read_to_memory()

self.memory.write(addr, received)

print(f" [内存内部] 地址 {addr} (0x{addr:04X}) ← {received} (0x{received:02X})")

time.sleep(0.001)

# T3: 各总线清空

print(f"\n[T3 - 清空总线]")

self.ctrl_bus.clear()

self.addr_bus.clear()

print(f"\n{'='*55}")

print(f"操作完成: {data} 已写入地址 {addr}")

print(f"{'='*55}")

# ===== 主程序: 运行演示 =====

if __name__ == "__main__":

print("=" * 60)

print(" 总线交互模拟 —— RUNOOB 计算机组成原理演示")

print("=" * 60)

print()

# 1. 初始化总线

print("【系统初始化】创建地址总线、数据总线、控制总线、内存模块")

addr_bus = AddressBus()

data_bus = DataBus()

ctrl_bus = ControlBus()

memory = SimpleMemory()

print()

# 2. 查看内存初始状态

print("【内存初始状态】关键地址处的数据预览:")

for addr in [50, 51, 52, 53, 54, 55, 100, 200]:

ch = chr(memory.data[addr]) if 32 <= memory.data[addr] < 127 else '?'

print(f" 地址 {addr} (0x{addr:04X}): {memory.data[addr]:3d} (0x{memory.data[addr]:02X}) '{ch}'")

print()

# 3. 初始化 CPU

cpu = CPU(addr_bus, data_bus, ctrl_bus, memory)

# 4. 演示:从内存读数据

print("\n" + "▼" * 60)

print("演示 1: 从内存地址 100 读取数据")

print("▼" * 60)

val1 = cpu.memory_read(100)

print("\n" + "▼" * 60)

print("演示 2: 从内存地址 200 读取数据")

print("▼" * 60)

val2 = cpu.memory_read(200)

print("\n" + "▼" * 60)

print("演示 3: 从内存地址 50-55 连续读取,拼出字符 (RUNOOB)")

print("▼" * 60)

chars = []

for addr in range(50, 56):

val = cpu.memory_read(addr)

chars.append(chr(val))

print(f"\n拼出的字符串: {''.join(chars)}")

# 5. 演示:向内存写数据

print("\n" + "▼" * 60)

print("演示 4: 向内存地址 10 写入数据 77")

print("▼" * 60)

cpu.memory_write(10, 77)

# 6. 验证写入结果

print("\n" + "▼" * 60)

print("演示 5: 验证写入——从地址 10 读回数据")

print("▼" * 60)

val3 = cpu.memory_read(10)

print(f"\n验证: 写入 77, 读出 {val3}, {'成功' if val3 == 77 else '失败'}")

print("\n" + "=" * 60)

print(" 全部演示完成!")

print("=" * 60)

总线宽度与实际性能

地址总线的宽度决定「能管多大的内存」

地址总线每条线传输一个二进制位。N 条地址线可以表示 2^N 个不同的地址。

地址总线宽度可寻址空间典型的 CPU

16 位2^16 = 64 KBIntel 8086 (1978)

20 位2^20 = 1 MBIntel 8088 (IBM PC, 1981)

24 位2^24 = 16 MBIntel 80286 (1982)

32 位2^32 = 4 GBIntel 80386 (1985)

64 位2^64 = 约 16 EB现代 x86-64 处理器

注意:现代 64 位 CPU 的实际地址线通常少于 64 根(例如 48 根),因为 2^64 字节远超当前物理内存的实际需求。48 根地址线已经可以寻址 256 TB,足够当前使用。

数据总线的宽度决定「单次能传多少数据」

8 位数据总线每次传 1 字节,64 位数据总线每次传 8 字节。这就是为什么同样的任务,64 位系统往往比 32 位系统更快——单次传输的数据量翻了一倍。

控制总线的信号种类

控制总线不像地址和数据总线那样整齐划一。它是多种信号线的集合,包括:

MEM_READ / MEM_WRITE:内存读写使能信号

IO_READ / IO_WRITE:I/O 端口读写信号

INTERRUPT:中断请求信号——外部设备通知 CPU 的方式

CLOCK:时钟信号——驱动整个系统同步工作的「心跳」

RESET:复位信号——让 CPU 回到初始状态

小结与检验

一句话总结:地址总线负责「去哪里」,数据总线负责「运什么」,控制总线负责「做什么」——三条总线各司其职,协同完成每一次数据传输。

自测题

CPU 要从内存读一个数据,地址总线上的信息是谁放上去的?数据总线上的信息又是谁放上去的?

如果一台计算机的地址总线只有 16 根线,它最多能管理多大的内存?

控制总线和地址总线、数据总线最大的不同是什么?(提示:想想控制总线的「组成」)

参考答案:1. 地址由 CPU 放上去,数据由内存放上去。2. 2^16 = 65536 字节 = 64KB。3. 地址和数据总线传输的是整齐的数字信号(地址值/数据值),而控制总线是一组各不相同的离散信号线,每根线有自己独立的功能。