26. 输入输出设备
计算机系统的最后一块拼图
孤独的 CPU
CPU 只是 “无情的指令执行机器”
- 取指令、译码、执行
Altair-8800 (1975), with Intel 8080A; 256B 板卡 RAM
(你需要在面板上拨动开关输入执行指令的起始地址)
实现输入/输出
例子:发射核弹
- 使计算机能感知外部状态 (眼睛、耳朵)、对外实施动作 (手)
GPIO (General Purpose Input/Output)
- 极简的模型:Memory-mapped I/O 直接读取/写入电平信号
GPIO: 一根可以读写数据的线
1 | led = LED(2) |
- 真正的核弹发射器也是类似的原理……
I/O 设备:“计算” 和 “物理世界” 之间的桥梁
I/O 设备 = 一个能与 CPU 交换数据的接口/控制器
- 就是 “几组约定好功能的线” (寄存器)
- 通过握手信号从线上读出/写入数据
- 给寄存器 “赋予” 一个内存地址 (Address Decoder)
- CPU 可以直接使用指令 (in/out/MMIO) 和设备交换数据
例子 (1): 串口 (UART)
“COM1” (Communication 1)
1 |
|
例子 (2): 键盘控制器
IBM PC/AT 8042 PS/2 (Keyboard) Controller
- Port 0x60 (data), 0x64 (status/command)
- command = 0xED → LED 灯控
- command = 0xF3 → 设置重复速度和重复延迟
- PS/2 接口的 6 根线分别是什么作用?
例子 (3): 磁盘控制器
ATA (Advanced Technology Attachment)
- IDE 接口磁盘 (40pin data 很 “肥” 的数据线 + 4pin 电源)
- primary: 0x1f0 - 0x1f7; secondary: 0x170 - 0x177
1 | void readsect(void *dst, int sect) { |
持久化存储:复杂性的分解
1-Bit 的存储
- 磁、坑、电……
- 无论如何存储,设备都使用 “处理器能理解” 的接口
Serial ATA 接口
- “几根导线”,可以工作在 IDE Mode/AHCI Mode
打印机是个怎样的设备?
- 打印机将字节流描述的文字/图形打印到纸张上
PostScript 和打印机
PostScript 一种描述页面布局的 DSL (Page DL)
- 类似于汇编语言 (由 “编译器”,如 latex,生成)
- PDF 是 PostScript 的 superset
打印机 (没错,实现自己的打印机没有那么困难)
- 将汇编语言翻译成机械部件动作的设备
- PCL, PostScript, 甚至直接支持 PDF
1 | <ESC>*t300R // Set resolution to 300 DPI |
管理更多的 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:只有一根中断线
我们需要一个 “仲裁器”
收集各个设备中断,并选择一个发送给 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 | for (int i = 0; i < 1 GB / 4; 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 色)……
一个简单的答案:加个 CPU
类比:DMA 是个 “降级” 的 CPU
- 只能执行 (半) 固定程序;但是电路更简单、执行速度更快、内置并行
在系统里加一个专门画图的 CPU?
1 | for (int x = 0; x < W; x++) |
- 不再需要那么多通用寄存器,循环可以直接用计数器实现
NES Picture Processing Unit (PPU)
Sprite Spec: 位置 + 1-bit Priority; 1-bit Fip (H/V)
榨干 PPU 的每一点性能
前景 + 背景 = 给定机能下的极限图形效果
- 前景:《魂斗罗》中角色为什么要「萝莉式屈腿俯卧」?
- 背景:“卷轴” 操作
以假乱真的贴图 3D
GameBoy Advance
- 4 层背景; 128 个剪贴 objects; 32 个 affine objects
- CPU 给出描述;GPU 绘制 (执行 “一个程序” 的 CPU)
真正的 3D 图形
构建一个三角形的世界
- 给每个面涂上颜色 (基本原理)
走向 “更真实” 的 3D
全靠 “PS”
- 虚假的真实感:Screen Space Ambient Occlusion
- 真实的真实感:Ray Tracing
Gefore 256 DDR (1999)
支持硬件 Transform & Lighting (更通用的计算)
走向异构计算
让更 “合适” 的 CPU 做他们更擅长的事
- “固定” 的循环:不止是游戏图形
1 | for (int x = 0; x < W; x++) |
- mandelbrot.c (科学计算)
- gpt.c (机器学习)
- 神似,但更 “复杂” (需要更通用的计算)
- ……
现代 GPU: 一个通用计算设备
类比:打印机
- 执行 PCL 代码 v.s. 执行 PTX 代码
一个完整的众核多处理器系统
- 拥有自己的内存 (显存)
- 可以把一段显存作为 FrameBuffer
- Vulkan: “Swap Chain”
- 显示控制器可以把一段像素 “直接输出” 到接口 (VGA, DisplayPort, HDMI) 上 (可以理解成显卡上的一个 DMA)
- 正确的时序输出正确的信号 = 显示器能正确显示
输入/输出设备是 “与处理器交换数据” 接口——因此,我们的设备可以实现得任意复杂,甚至是一个完整的计算机系统。从我们今天的打印机、SSD、GPU,都遵循了这个模式,在 CPU 的统一管理和调度下各自完成各自的功能。