一、引言
1、什么是 Linux 的 IO 模型
Linux IO 模型是 Linux 操作系统中的 IO 处理机制,IO 就是数据在硬盘、内存、网卡等 IO 设备间移动的过程。
2、阅读本文的知识准备
(1)用户空间和内核空间
参见这篇文章 用户空间和内核空间
(2)file descriptor(文件描述符)
Linux 中一切皆文件,比如视频文件、可执行文件、键盘、显示器等等。Linux 为每个已打开的文件分配了一个编号,称为文件描述符,即文件唯一标识。
(3)阻塞与非阻塞说的是什么
阻塞与非阻塞关注的是应用程序调用 IO 后等待数据返回时的状态,简单说就是数据未就绪时是否等待。
阻塞指的是应用程序调用 IO 操作时,需要内核 IO 操作完成后才返回用户空间;非阻塞指的是应用程序调用 IO 操作后,内核立即返回一个值。
(4)同步与异步说的是什么
同步异步关注的是任务完成时的消息通知方式
同步指的是应用程序发起 IO 操作后,需要等待或轮询内核;异步指的是 IO 操作完成后,通知应用程序。
二、IO 模型种类
Blocking IO - 阻塞式 IO
以应用程序读取 socket 数据举例。
- 应用程序发起系统调用,读取 socket 数据,此时处于用户态
- 进入内核态,等待网卡数据到达,到达后复制网卡数据到内核空间,再从内核空间复制数据到用户空间;返回应用程序(在此期间应用程序一直处于阻塞状态)
- 此时处于用户态,应用程序处理数据。
从上述过程可以看出 Blocking IO 既是同步也是阻塞。期间经过了两次数据copy,经过了两次用户态/内核态切换。
阻塞式 IO 的优点就是简单易懂,缺点是进程/线程期间阻塞时间长,浪费资源,性能低下。
注: read() 和 recvfrom() 的区别是,前者从文件系统读取数据,后者从 socket 接收数据。
Nonblocing IO - 非阻塞式 IO
以应用程序读取 socket 数据举例。
应用程序发起系统调用,读取 socket 数据,如果数据没有就绪,直接返回相应code,应用程序一直发起轮询,直到数据就绪。
非阻塞式 IO 的优点在于进程/线程发起 IO 操作时,如果数据没有就绪,不会阻塞。
缺点在于消耗 CPU 资源,至少多了一次系统调用,如果是本地文件读取的话可能还快点,对 socket 来说就会有大量空轮询,就比较鸡肋。
IO Multiplexing - IO 多路复用
IO 多路复用优化了非阻塞 IO 大量空轮询的问题。
底层实现有select、poll、epoll,不同操作系统支持的不同。以select举例:
- 应用程序发起系统调用,注册 fd 感兴趣的事件
- 内核发现有 fd 就绪,返回数量
- 程序轮询就绪的 fd_set
- 发起真正的 IO 操作
select 缺点: fd_set 有大小限制;需要轮询 fd,性能会有所下降。
poll fd_set 不受限制;
epoll 直接返回就绪的fd,不需要轮询。现在linux中的 tomcat、netty都有epoll实现。
从上述过程可以看出,IO 多路复用是同步非阻塞。
Signal-Driven IO - 信号驱动 IO
可以简单理解为一种异步回调,数据就绪时系统回调用户函数。
没用使用过,实际很少使用,具体缺点可以参考网上相关文章。