(2021) 23 [持久化] I/O设备与驱动
南京大学操作系统课蒋炎岩老师网络课程笔记。
视频:https://www.bilibili.com/video/BV1HN41197Ko?p=23
讲义:http://jyywiki.cn/OS/2021/slides/13.slides#/
背景
很多人 (你们的同学们、家长们) 都有一个认识,“计算机系就是装 (修) 电脑的”,因为大家对电脑的印象只有 I/O 设备。但上了计算机系发现根本不是这么回事啊,根本没有直接教怎么 “修电脑”。
但不应该是这样的!
- 学 “计算机” 的人不仅会修电脑,还会造电脑!
只要我们有一个设备的手册或者文档等充足的信息,就可以结合自己在操作系统等课程上的知识,来查找并修复问题。
本次课的内容和目标
理解 “I/O 设备是什么”
- 键盘
- 磁盘
- 中断控制器
- 总线
- DMA
- GPU
学习 I/O 设备在操作系统中的抽象
- 设备 = 可以读、写、控制的对象
- “设备驱动程序”
常见 I/O 设备
孤独的CPU
CPU 只是 “无情的指令执行机器”,不断地从内存上进行取指令、译码、执行。
我们是通过各种各样IO设备实现与CPU的交互的。
CPU眼中的IO设备
I/O 设备是一个能与 CPU 交换数据的接口
通俗来讲:
- 就是 “几组线”
- 每一组线有约定的功能 (RTFM)
- CPU 通过握手信号从线上读出/写入数据
- 每一组线有自己的地址
- CPU 可以直接使用指令 (in/out/MMIO) 和设备交换数据
在CPU看来,与常见的IO设备的交互,就是按照特定的规则(RTFM)来读写一组寄存器。比如下面介绍的键盘控制器和磁盘控制器。
键盘控制器
IBM PC/AT 8042 PS/2 (Keyboard) Controller
- “硬编码” 到两个 I/O port:
0x60
(data),0x64
(status/command)
Command Byte | Use | 说明 |
---|---|---|
0xED | LED 灯控 | ScrollLock/NumLock/CapsLock |
0xF3 | 设置重复速度 | 30Hz - 2Hz; Delay: 250 - 1000ms |
0xF4/0xF5 | 打开/关闭 | N/A |
0xFE | 重新发送 | N/A |
0xFF | RESET | N/A |
参考 AbstractMachine 的键盘部分实现
磁盘控制器
ATA (Advanced Technology Attachment)
- IDE (Integrated Drive Electronics) 接口磁盘
- primary:
0x1f0 - 0x1f7
; secondary:0x170 - 0x177
- primary:
void readsect(void *dst, int sect) {waitdisk();out_byte(0x1f2, 1); // sector count (1)out_byte(0x1f3, sect); // sectorout_byte(0x1f4, sect >> 8); // cylinder (low)out_byte(0x1f5, sect >> 16); // cylinder (high)out_byte(0x1f6, (sect >> 24) | 0xe0); // driveout_byte(0x1f7, 0x20); // command (write)waitdisk();for (int i = 0; i < SECTSIZE / 4; i ++)((uint32_t *)dst)[i] = in_long(0x1f0); // data
}
总之,以上这些IO设备,在CPU看来,就是按照特定的规则(RTFM)来读写一组寄存器。
特殊的IO设备
中断控制器
我们的设备中通常有许多IO设备,那我们只想在这些IO设备有有意义的数据/信号时,CPU才回去处理这些数据,而在平时IO设备没有数据时,不要打扰CPU的其他正常工作。这就要通过中断来实现。
CPU有一个中断引脚收到某个特定的电信号会触发中断
- 保存 5 个寄存器 (cs, rip, rflags, ss, rsp)
- 跳转到中断向量表对应项执行
CPU有且只有一个中断引脚(如图中6502的4号引脚 IRQˉ\bar{IRQ}IRQˉ)。那CPU是怎样管理各种不同的中断,以及不同中断到来时的处理优先级的呢?这就要用到一个特殊的IO设备:中断控制器。
系统中的其他设备可以向中断控制器连线。从而实现上面提到的中断优先级的判断、中断屏蔽等功能。
- Intel 8259 PIC(programmable interrupt controller),(微机原理中也介绍过,配合8086),可以设置中断屏蔽、中断触发等……
- APIC (Advanced PIC)(现代CPU中使用的中断控制器)
- local APIC(每个CPU内部的中断管理): 中断向量表, IPI, 时钟, ……
- I/O APIC(外部IO的中断管理): 其他 I/O 设备
总线
想法
如果你只造 “一台计算机”,随便给每个设备定一个端口/地址,用 mux 连接到 CPU 就行。
但如果你希望给未来留点空间?
- 想卖大价钱的 “大型机”
- IBM, DEC, …
- 车库里造出来的 “微型机”
- 名垂青史的梦想家
- 都希望接入更多 I/O 设备
- 甚至是未知的设备,即可扩展性
在多个IO设备时,每个IO设备中的每个寄存器都有一个自己的地址,那肯定不能将这些寄存器全部直连到CPU上。
总线介绍
总线提供地址到设备的转发。把从CPU传来的地址(总线地址)和数据转发到相应的设备上(按照上面说的每个设备中的每个寄存器的地址)。例如: port I/O 的端口就是总线上的地址,IBM PC 的 CPU 其实只看到这一个 I/O 设备。
这样 CPU 只需要直连一个总线 (例如今天的 PCI总线 (Peripheral Component Interconnect) 就行了
- 总线可以负责IO设备寄存器的地址编址
- 总线可以桥接其他总线 (例如 PCI → USB)
lspci -tv
和lsusb -tv
: 查看系统中总线上的设备- 概念简单,实际非常复杂
- 电气特性、burst 传输、中断……
例子:PCI Device Probe
DMA(Direct Memory Access)
想法
假设程序希望写入 1 GB 的数据到磁盘
- 即便磁盘已经准备好,依然需要非常浪费缓慢的循环
- out 指令写入的是设备缓冲区,需要去总线上绕一圈
- cache disable; store 其实很慢的
for (int i = 0; i < 1 GB / 4; i++) {outl(PORT, ((u32 *)buf)[i]);
}
能否把 CPU 从执行循环中解放出来?
- 比如,在系统里加一个 CPU,专门复制数据?
- 好像
memcpy_to_port(ATA0, buf, length);
DMA介绍
-
DMA可以理解为一个只能执行memcpy这一条指令的迷你处理器,它只负责直接将内存上的内容搬运的磁盘。而不需要真正的CPU的参与,不需要占用真正的CPU时间。实际实现:直接把 DMA 控制器连接在总线和内存。
-
由于DMA处理器只进行memcpy这样一条指令,因此它的实现比通用的真正的CPU简单得多。
-
DMA不仅可以完成内存和设备之间的内容搬运,而也可以是内存和内存之间。即可以有:
- memory → memory
- memory → device (register)
- device (register) → memory
-
PCI 总线支持 DMA,即PCI中自带DMA,这就是为什么 CPU 会有 PCIe lanes。
GPU
在CPU眼中,显卡(GPU)也是一种IO设备,通过读写某些寄存器来交互。
一切皆可计算
for (int i = 1; i <= H; i++) {for (int j = 1; j <= W; j++)putchar(j <= i ? '*' : ' ');putchar('\n');
}
难办的是性能
- NES: 6502 @ 1.79Mhz; IPC = 0.43
- 屏幕共有 256 x 240 = 61K 像素 (256 色)
- 60FPS → 每一帧必须在 ~10K 条指令内完成
- 如何在有限的 CPU 运算力下实现 60Hz?
既然能做一个只专注于 memcpy 的硬件DMA,为什么不能做一个画图的硬件?在输出图形时,可以认为对每个像素点做的是相同的计算指令,不同的数据的计算,因此,可以认为是一种并行计算。因此,我们也可以用一个专注于处理并行计算的非通用功能的处理器:GPU,只用来处理图形相关的这类并行计算。
现代GPU:一个通用计算设备
一个完整的众核多处理器系统
- 注重大量并行相似的任务
- 程序使用例如 OpenGL, CUDA, OpenCL, … 书写
- 程序保存在内存 (显存) 中
- nvcc: 把 main 编译/链接成 ELF; kernel 编译成 GPU 指令
- 数据也保存在内存 (显存) 中
- 可以输出到视频接口 (DP, HDMI, …)
- 也可以通过 DMA 传回系统内存
gpgpu:通用图形处理器
通过对处理图形显示这类并行计算任务的推广,出现了通用图形处理器。它们不再只关注图形处理,而是可以针对更广范围的并行计算,比如神经网络的计算、矩阵计算。
通用图形处理器(General-purpose computing on graphics processing units,简称GPGPU),是一种利用处理图形任务的图形处理器来计算原本由中央处理器处理的通用计算任务。这些通用计算常常与图形处理没有任何关系。由于现代图形处理器强大的并行处理能力和可编程流水线,令流处理器可以处理非图形数据。特别在面对单指令流多数据流(SIMD),且数据处理的运算量远大于数据调度和传输的需要时,通用图形处理器在性能上大大超越了传统的中央处理器应用程序。
I/O设备的抽象
I/O设备是操作系统中的对象(文件)
无论何种 I/O 设备,都是可以读 (read) 写 (write) 的字节序列 (流或数组)。
I/O设备应该是操作系统中怎样的对象?操作系统最简单,或者说最不负责任的做法,当然是可以直接将IO设备的寄存器,或者PCI总线的控制寄存器,暴露给应用程序。微内核的操作系统就是这么做的,微内核的系统只做最少、最必要的部分。
但如果操作系统再负责一点,将我们的每个IO设备(比如键盘、磁盘等)都抽象成操作系统中的一个对象。
操作系统:设备 = 支持以下三种操作的对象 (文件)
- read: 从设备某个指定的位置读出数据
- write: 向设备某个指定位置写入数据
- ioctl: 读取/设置设备的状态
而以上这三种操作,恰恰就是文件系统中,一个文件应该支持的操作。 Everything is a file !
设备驱动程序:实现抽象
设备驱动程序把对设备的 read/write/ioctl 系统调用 “翻译” 成设备的寄存器命令序列。
以 “面向对象” 的方式访问 I/O 设备,设备 = 支持 read, write, ioctl, … 功能的对象。
例子:/dev 中的对象
/dev/pts/[x]
- pseudo terminal/dev/zero
- “零” 设备/dev/null
- “null” 设备,一般不想要的输出可以直接重定位到这个设备。/dev/random
,/dev/urandom
- 随机数生成器
未必一定要有物理设备
设备驱动程序:将设备抽象为一个对象和操作,未必一定要有物理设备,比如/dev/null
, /dev/urandom
。就是操作系统中不需要物理设备的一种设备/文件/对象的抽象。
试一试
-
试一试执行命令:
head -c 512 [device] | xxd
,并观察它们的strace
来查看访问设备的系统调用。 -
tty
查看当前终端,输出/dev/pts/3
,即当前终端是3号终端。如果我们再打开一个终端并向之前的3号终端输出,是可以直接出现在3号终端上的:
echo Hello > /dev/pts/3
。也就是说,每个打开的终端也被看做是在设备目录
/dev
下的一个设备。
存储设备的抽象
磁盘 (存储设备) 的访问特性与其他的设备的读写略有不同,是以块为单位的。
- 以数据块 (block) 为单位访问,传输有 “最小单元”,块内不支持任意随机访问
- 大吞吐量,使用 DMA 传送数据。
- 应用程序不直接访问
- 访问者通常是文件系统 (维护磁盘上的数据结构)
- 大量并发的访问 (操作系统中的进程都要访问文件系统)
对比一下终端和 GPU,的确是很不一样的设备
- 终端:小数据量、直接流式传输
- GPU:大数据量、DMA 传输
总结
本次课内容与目标
- 理解 “什么是 I/O 设备”
- 终端、键盘、鼠标、总线、DMA、GPU……
- 理解 “I/O 设备在操作系统中的抽象”
- 可以读/写/控制的对象
Takeaway messages
- 如果你 “自己造一台计算机”,你会发现这一切都是自然的
- “不容易理解” 的部分是随时间积累的复杂性