计算机系统的最后一块拼图

孤独的 CPU

CPU 只是 “无情的指令执行机器”

  • 取指令、译码、执行

c26-1-1.webp

Altair-8800 (1975), with Intel 8080A; 256B 板卡 RAM
(你需要在面板上拨动开关输入执行指令的起始地址)

实现输入/输出

例子:发射核弹

  • 使计算机能感知外部状态 (眼睛、耳朵)、对外实施动作 (手)

GPIO (General Purpose Input/Output)

  • 极简的模型:Memory-mapped I/O 直接读取/写入电平信号

c26-1-2.webp

GPIO: 一根可以读写数据的线

c26-1-3.gif

1
2
led = LED(2)
led.on(); time.sleep(0.03); led.off()
  • 真正的核弹发射器也是类似的原理……

I/O 设备:“计算” 和 “物理世界” 之间的桥梁

I/O 设备 = 一个能与 CPU 交换数据的接口/控制器

  • 就是 “几组约定好功能的线” (寄存器)
    • 通过握手信号从线上读出/写入数据
  • 给寄存器 “赋予” 一个内存地址 (Address Decoder)
    • CPU 可以直接使用指令 (in/out/MMIO) 和设备交换数据

c26-1-4.webp

例子 (1): 串口 (UART)

“COM1” (Communication 1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define COM1 0x3f8

static int uart_init() {
outb(COM1 + 2, 0); // 控制器相关细节
outb(COM1 + 3, 0x80);
outb(COM1 + 0, 115200 / 9600);
...
}

static void uart_tx(AM_UART_TX_T *send) {
outb(COM1, send->data);
}

static void uart_rx(AM_UART_RX_T *recv) {
recv->data = (inb(COM1 + 5) & 0x1) ? inb(COM1) : -1;
}

例子 (2): 键盘控制器

IBM PC/AT 8042 PS/2 (Keyboard) Controller

  • Port 0x60 (data), 0x64 (status/command)
  • command = 0xED → LED 灯控
  • command = 0xF3 → 设置重复速度和重复延迟
    • PS/2 接口的 6 根线分别是什么作用?

c26-1-5.webp

例子 (3): 磁盘控制器

ATA (Advanced Technology Attachment)

  • IDE 接口磁盘 (40pin data 很 “肥” 的数据线 + 4pin 电源)
    • primary: 0x1f0 - 0x1f7; secondary: 0x170 - 0x177
1
2
3
4
5
6
7
8
9
10
11
12
void readsect(void *dst, int sect) {
waitdisk();
out_byte(0x1f2, 1); // sector count (1)
out_byte(0x1f3, sect); // sector
out_byte(0x1f4, sect >> 8); // cylinder (low)
out_byte(0x1f5, sect >> 16); // cylinder (high)
out_byte(0x1f6, (sect >> 24) | 0xe0); // drive
out_byte(0x1f7, 0x20); // command (write)
waitdisk();
for (int i = 0; i < SECTSIZE / 4; i ++)
((uint32_t *)dst)[i] = in_long(0x1f0); // data
}

持久化存储:复杂性的分解

1-Bit 的存储

  • 磁、坑、电……
  • 无论如何存储,设备都使用 “处理器能理解” 的接口

Serial ATA 接口

  • “几根导线”,可以工作在 IDE Mode/AHCI Mode

打印机是个怎样的设备?

c26-1-6.webp

  • 打印机将字节流描述的文字/图形打印到纸张上

PostScript 和打印机

PostScript 一种描述页面布局的 DSL (Page DL)

  • 类似于汇编语言 (由 “编译器”,如 latex,生成)
    • PDF 是 PostScript 的 superset

打印机 (没错,实现自己的打印机没有那么困难)

  • 将汇编语言翻译成机械部件动作的设备
    • PCL, PostScript, 甚至直接支持 PDF
1
2
3
4
5
6
7
<ESC>*t300R          // Set resolution to 300 DPI
<ESC>*r1A // Start raster graphics
<ESC>*b100W // Set width of raster data (100 bytes)
<ESC>*b0M // Set compression mode (0 = uncompressed)
<ESC>*b100V // Send 100 bytes of raster data
<binary raster data> // Actual image data
<ESC>*rB // End raster graphics

管理更多的 I/O 设备

(1) 我们不想造一台 “设备定死” 的计算机

接入更多 (甚至未知) 的 I/O 设备

  • 想卖大价钱的 “大型机”:IBM, DEC, …
  • 车库里造出来的 “微型机”:名垂青史的梦想家
    • IBM PC/AT: ISA (Industry Standard Architecture) 总线
    • Apple II: 50-pin slot connector (Apple II Bus)

总线:一个特殊的 I/O 设备

提供设备的 “虚拟化”:注册和转发

  • 把收到的地址 (总线地址) 和数据转发到相应的设备上
  • 例子: port I/O 的端口就是总线上的地址
    • IBM PC 的 CPU 其实只看到这一个 I/O 设备

这样 CPU 只需要直连一个总线就行了!

  • 今天 PCI 总线肩负了这个任务
    • 总线可以桥接其他总线 (例如 PCI → USB)
  • lspci -tv 和 lsusb -tv: 查看系统中总线上的设备
    • 概念简单,实际非常复杂……
      • 电气特性、burst 传输、中断、Plug and Play

总线:名场面

视频地址

(2) CPU:只有一根中断线

c26-2-1.webp

我们需要一个 “仲裁器”

收集各个设备中断,并选择一个发送给 CPU

  • 并且完成对设备的应答

例子

  • Intel 8259 PIC
    • programmable interrupt controller
    • 可以设置中断屏蔽、中断触发等 ……
  • APIC (Advanced PIC)
    • local APIC: 中断向量表, IPI, 时钟, ……
    • I/O APIC: 其他 I/O 设备

(3) 解放 CPU 算力

操作系统:写入 1 GB 的数据到磁盘

  • 即便磁盘已经准备好,依然需要非常浪费时间的循环
  • out 指令写入的是设备缓冲区,需要去总线上绕一圈
    • cache disable; store 其实很慢的
1
2
3
for (int i = 0; i < 1 GB / 4; i++) {
outl(PORT, ((u32 *)buf)[i]);
}

如果是多处理器系统?

  • 那就把 write_disk 线程扔到另一个 CPU

Direct Memory Access (DMA)

加一个通用处理器太浪费,不如加一个简单的

  • DMA: 只能执行 memcpy(ATA0, buf, length); 的处理器
  • 支持的几种类型的 memcpy
    • memory → memory
    • memory → device (register)
    • device (register) → memory
      • 实际实现:直接把 DMA 控制器连接在总线和内存上
      • Intel 8237A

今天:PCI 总线支持 DMA

  • sudo cat /proc/iomem

填补 CPU 的算力空白

一个有趣的事实

计算机系统里充满了 “CPU”

  • CPU: 大核 + 小核 + 超小核
  • DMA: 执行 memcpy() 的 CPU
  • 打印机:解析执行 PCL/PostScript 的 CPU
  • 网卡:分拣以太网 packet (frame) 的 CPU
    • 它们都受到 CPU 的统一调配

有需求,就会有更多的 “CPU”!

人类的本质需求:娱乐

NES: 售出超过 60, 000, 000 台 (PS2, NDS 都破 1.5 亿了)

  • 在 MOS 6502 @ 1.79Mhz (IPC = 0.43) 上实现 60 FPS
    • 每一帧必须在 ~10K 条指令内完成
    • 但屏幕共有 256 x 240 = 61K 像素 (256 色)……

c26-3-1.png

一个简单的答案:加个 CPU

类比:DMA 是个 “降级” 的 CPU

  • 只能执行 (半) 固定程序;但是电路更简单、执行速度更快、内置并行

在系统里加一个专门画图的 CPU?

1
2
3
4
5
6
for (int x = 0; x < W; x++)
for (int y = 0; y <= H; y++)
for (int i = 0; i < n; i++)
if (intersects(sprites[i], x, y)) {
fb[x][y] = sprites[i].pixel(x, y);
}
  • 不再需要那么多通用寄存器,循环可以直接用计数器实现

NES Picture Processing Unit (PPU)

Sprite Spec: 位置 + 1-bit Priority; 1-bit Fip (H/V)

c26-3-2.png

榨干 PPU 的每一点性能

前景 + 背景 = 给定机能下的极限图形效果

c26-3-1.gif

以假乱真的贴图 3D

GameBoy Advance

  • 4 层背景; 128 个剪贴 objects; 32 个 affine objects
    • CPU 给出描述;GPU 绘制 (执行 “一个程序” 的 CPU)

c26-3-1.webp

真正的 3D 图形

构建一个三角形的世界

c26-3-2.webp

Tomb Raider (1996)

走向 “更真实” 的 3D

c26-3-3.webp

全靠 “PS”

  • 虚假的真实感:Screen Space Ambient Occlusion
  • 真实的真实感:Ray Tracing

Gefore 256 DDR (1999)

支持硬件 Transform & Lighting (更通用的计算)

c26-3-4.webp

走向异构计算

让更 “合适” 的 CPU 做他们更擅长的事

  • “固定” 的循环:不止是游戏图形
1
2
3
4
5
6
for (int x = 0; x < W; x++)
for (int y = 0; y <= H; y++)
for (int i = 0; i < n; i++)
if (intersects(sprites[i], x, y)) {
fb[x][y] = sprites[i].pixel(x, y);
}
  • mandelbrot.c (科学计算)
  • gpt.c (机器学习)
    • 神似,但更 “复杂” (需要更通用的计算)
  • ……

现代 GPU: 一个通用计算设备

类比:打印机

  • 执行 PCL 代码 v.s. 执行 PTX 代码

一个完整的众核多处理器系统

  • 拥有自己的内存 (显存)
  • 可以把一段显存作为 FrameBuffer
    • Vulkan: “Swap Chain”
    • 显示控制器可以把一段像素 “直接输出” 到接口 (VGA, DisplayPort, HDMI) 上 (可以理解成显卡上的一个 DMA)
      • 正确的时序输出正确的信号 = 显示器能正确显示

输入/输出设备是 “与处理器交换数据” 接口——因此,我们的设备可以实现得任意复杂,甚至是一个完整的计算机系统。从我们今天的打印机、SSD、GPU,都遵循了这个模式,在 CPU 的统一管理和调度下各自完成各自的功能。