Unix IO 模型
一、Unix IO 操作的基本阶段
一个输入操作通常分为两个阶段:
- 等待数据准备好:例如,对于套接字输入操作,等待数据从网络到达并被复制到内核缓冲区。
- 从内核向进程复制数据:将数据从内核缓冲区复制到应用进程缓冲区。
二、Unix 五种 IO 模型
Unix 提供了以下五种 IO 模型:
- 阻塞式 IO
- 非阻塞式 IO
- IO 复用(select 和 poll)
- 信号驱动式 IO(SIGIO)
- 异步 IO(AIO)
三、IO 模型详解
1. 阻塞式 IO
- 特点:应用进程在数据从内核复制到应用进程缓冲区完成前一直处于阻塞状态。
- 过程:调用 recvfrom()(接收套接字数据)时,进程等待直到数据准备好并复制完成。
- 优点:
- 在阻塞期间,其他程序仍可执行,不占用 CPU 时间。
- 执行效率较高。
2. 非阻塞式 IO
- 特点:系统调用立即返回,若数据未准备好,则返回错误码。应用进程需通过轮询(polling)不断检查 IO 是否完成。
- 过程:调用后若数据未就绪,内核返回错误,进程继续运行但需反复调用。
- 缺点:频繁的系统调用增加 CPU 开销,效率较低。
3. IO 复用(select 和 poll)
- 特点:使用 select 或 poll 等待多个套接字中的任一变为可读,等待阶段阻塞,返回后用 recvfrom 复制数据。
- 优点:
- 单进程可处理多个 IO 事件(事件驱动 IO)。
- 系统开销小,无需为每个连接创建线程,适合高并发场景(如 Web 服务器)。
- 应用:相比多线程,IO 复用避免了线程创建和切换的开销。
4. 信号驱动式 IO(SIGIO)
- 特点:应用进程调用 sigaction,等待数据阶段非阻塞,数据到达时内核发送 SIGIO 信号,进程在信号处理程序中调用 recvfrom 获取数据。
- 优点:相比非阻塞式 IO 的轮询,CPU 利用率更高。
5. 异步 IO(AIO)
- 特点:调用 aio_read 后立即返回,进程不阻塞,内核完成所有操作后发送信号通知进程。
- 与信号驱动 IO 的区别:
- 异步 IO:信号表示 IO 已完成。
- 信号驱动 IO:信号表示可以开始 IO。
四、IO 模型比较
1. 同步 IO 与异步 IO
- 同步 IO:调用 recvfrom 时进程阻塞,包括阻塞式 IO、非阻塞式 IO、IO 复用和信号驱动 IO。
- 非阻塞式和信号驱动 IO 在等待阶段不阻塞,但在数据复制阶段仍阻塞。
- 异步 IO:全程不阻塞。
2. 五大 IO 模型对比
- 第一阶段(等待数据):四种同步模型在此阶段行为不同。
- 第二阶段(数据复制):除异步 IO 外,其他模型均阻塞。
- 总结:异步 IO 是唯一全程非阻塞的模型。
五、IO 多路复用详解
1. 工作模式(以 epoll 为例)
- LT 模式(Level Trigger,水平触发):
- 默认模式,事件未处理时,epoll_wait() 再次调用仍会通知。
- 支持阻塞和非阻塞。
- ET 模式(Edge Trigger,边缘触发):
- 事件通知一次,需立即处理,之后不再重复通知。
- 仅支持非阻塞,效率更高,避免阻塞操作饿死其他任务。
2. 应用场景
- select:
- 优点:精度高(1ns),可移植性强。
- 适用:实时性要求高(如核反应堆控制)。
- poll:
- 优点:无最大描述符限制。
- 适用:监控少于 1000 个描述符,实时性要求不高。
- epoll:
- 优点:高效处理大量描述符。
- 适用:Linux 平台,长连接、高并发场景。
- 注意:
- poll 和 epoll 精度为 1ms。
- epoll 在频繁状态变化的短暂连接中效率较低,因需频繁系统调用。
六、总结
- 阻塞式 IO:简单高效,适合低并发。
- 非阻塞式 IO:灵活但低效,轮询耗 CPU。
- IO 复用:高并发利器,单进程处理多事件。
- 信号驱动 IO:减少轮询,提升效率。
- 异步 IO:全程非阻塞,最高效但实现复杂。
- IO 多路复用:select、poll、epoll 各有优势,需根据场景选择。