4.1 IO系统概述
IO(Input/Output,输入输出)系统是计算机系统中负责与外部设备进行数据交互的子系统。CPU和内存的速度提升非常快,但IO设备的速度提升相对较慢,IO系统往往是整个系统的性能瓶颈。
IO系统的层次结构
计算机的IO系统是一个分层的架构,每层负责不同的功能,上层不需要关心下层的具体实现细节:
┌─────────────────────────┐
│ 应用程序层 │ → 调用标准库的IO接口
├─────────────────────────┤
│ 标准库层 │ → 封装系统调用,提供易用的IO接口(如C的stdio、Java的IO类)
├─────────────────────────┤
│ 操作系统层 │ → 系统调用、文件系统、设备驱动、IO调度
├─────────────────────────┤
│ 设备控制器 │ → 硬件层面的控制,如磁盘控制器、网卡控制器
├─────────────────────────┤
│ 物理设备层 │ → 实际的硬件设备(硬盘、网卡、键盘等)
└─────────────────────────┘
各层的职责:
- 应用程序层:发起IO请求,比如读取文件、发送网络请求
- 标准库层:封装了底层的系统调用,提供跨平台的、易用的IO接口,不需要开发者直接和系统调用打交道
- 操作系统层:负责管理所有IO设备,提供系统调用接口,处理IO调度,保证公平性和效率
- 设备控制器:是硬件和软件之间的接口,负责控制具体的硬件设备,完成实际的IO操作
- 物理设备层:实际的硬件设备,负责最终的输入输出操作
IO控制方式
CPU和设备之间的数据传输有几种不同的控制方式,效率差异很大:
1. 程序控制IO(轮询方式)
- 工作原理:CPU不断轮询检查设备的状态,直到设备准备好后才进行数据传输
- 优点:实现简单,不需要额外的硬件支持
- 缺点:CPU利用率极低,大部分时间都在空转等待
- 适用场景:简单的嵌入式系统,或者速度非常慢的设备
2. 中断驱动IO
- 工作原理:CPU发起IO请求后就去做其他事情,设备准备好后向CPU发送中断信号,CPU响应中断后处理数据传输
- 优点:CPU不需要等待,利用率比轮询高很多
- 缺点:每个字的传输都需要中断,大量IO时中断次数太多,会消耗大量CPU资源
- 适用场景:字符设备、输入设备等低速IO设备
3. DMA(直接内存访问)
- 工作原理:DMA控制器接管数据传输的工作,CPU只需要告诉DMA控制器要传输的数据地址、长度和方向,DMA控制器自动完成整个数据块的传输,传输完成后再向CPU发送中断
- 优点:CPU不需要参与数据传输过程,大大降低了CPU的负担,适合大量数据的传输
- 缺点:需要额外的DMA控制器硬件支持
- 适用场景:磁盘、网卡等块设备,需要大量数据传输的场景
4. 通道方式
- 工作原理:通道是一个专门的IO处理器,有自己的指令集,可以独立执行通道程序,完成更复杂的IO操作,不需要CPU干预
- 优点:CPU的负担更小,适合大型计算机系统中大量IO的场景
- 缺点:硬件成本高,只在大型机、服务器中使用
- 适用场景:高端服务器、大型计算机系统
现在我们常用的PC和服务器中,大部分块设备(磁盘、网卡)都使用DMA方式进行数据传输,字符设备(键盘、鼠标)使用中断驱动方式。
IO性能指标
衡量IO性能主要有三个指标:
1. 带宽(Bandwidth)
- 单位时间内能够传输的数据量,单位通常是MB/s、GB/s
- 衡量的是IO系统的吞吐量,越大越好
- 比如SATA 3.0接口的带宽是6Gbps≈750MB/s,PCIe 4.0 x16的带宽是32GB/s
2. 延迟(Latency)
- 从发起IO请求到请求完成的时间,单位通常是ms(毫秒)、μs(微秒)
- 衡量的是IO系统的响应速度,越小越好
- 比如SSD的随机读延迟是0.1ms左右,HDD的随机读延迟是10ms左右
3. IOPS(Input/Output Operations Per Second)
- 每秒能够处理的IO请求数量
- 衡量的是IO系统处理小请求的能力,越大越好
- 比如普通HDD的IOPS是100-200左右,普通SSD的IOPS是10000-100000左右,高端NVMe SSD可以达到百万级IOPS
注意:这三个指标是独立的,高带宽不一定代表高IOPS,比如HDD的连续读写带宽可以达到100-200MB/s,但随机IOPS只有100左右,因为每次随机IO都需要寻道,延迟很高。
IO模型分类
在编程层面,我们通常从两个维度对IO模型进行分类:
维度1:是否阻塞
- 阻塞IO:发起IO请求后,线程会被挂起,直到IO操作完成才会继续执行
- 非阻塞IO:发起IO请求后,立即返回,不会阻塞线程,如果数据还没准备好,返回错误,应用程序可以轮询检查是否完成
维度2:是否同步
- 同步IO:IO操作由应用程序自己完成,或者需要应用程序主动等待操作完成
- 异步IO:IO操作由操作系统完成,操作完成后通知应用程序,整个过程应用程序不需要等待
常见的IO模型
- 阻塞IO(BIO):最传统的IO模型,线程发起IO请求后就阻塞等待,直到操作完成。优点是编程简单,缺点是线程利用率低,高并发场景下需要大量线程,开销大。
- 非阻塞IO:IO请求立即返回,线程可以做其他事情,定期轮询检查IO是否完成。优点是不阻塞线程,缺点是轮询会消耗CPU资源,效率低。
- IO多路复用(IO Multiplexing):用一个线程监听多个IO请求的状态,当某个请求准备好后,再处理这个请求。常见的实现有select、poll、epoll(Linux)、kqueue(BSD/macOS)、IOCP(Windows)。优点是线程利用率高,适合高并发场景,是现在高性能网络服务器的主流IO模型。
- 信号驱动IO:应用程序向操作系统注册IO信号,当IO准备好后,操作系统发送信号通知应用程序处理。用得比较少。
- 异步IO(AIO):应用程序发起IO请求后,立即返回,操作系统完成整个IO操作(包括数据拷贝)后通知应用程序。整个过程完全不阻塞线程,效率最高,但编程复杂,支持的平台有限。
高性能IO的发展方向:尽量减少线程阻塞,用尽可能少的线程处理尽可能多的IO请求,提升系统的并发能力。
IO系统的性能瓶颈
IO系统往往是整个计算机系统的性能瓶颈,原因是不同硬件之间的速度差异非常大:
- CPU和缓存:ns(纳秒)级,速度极快
- 内存:100ns级,速度很快
- SSD:100μs(微秒)级,比内存慢1000倍
- HDD:10ms(毫秒)级,比内存慢100000倍
- 网络:几十ms到几百ms级,跨地域的网络延迟更高
可以看到,存储和网络IO的速度比CPU和内存慢几个数量级,所以系统的整体性能往往被IO设备限制,这就是著名的“冯·诺依曼瓶颈“。
思考问题
- 为什么IO系统往往是计算机系统的性能瓶颈?
- DMA方式相比中断驱动方式有什么优势?为什么块设备都使用DMA方式?
- IO多路复用模型相比阻塞IO模型有什么优势?为什么高并发场景下都用IO多路复用?
- 带宽、延迟、IOPS三个指标之间有什么关系?分别适合衡量什么样的IO场景?