1. 图示 Nginx 服务处理请求简略过程
2. I/O 模型概述
2.1 同步/异步
同步:Synchronous,调用者等待被调用者返回消息,才能继续执行。
异步:Asynchronous,被调用者通过状态、通知或回调机制主动通知调用者自己的运行状态。
2.2 阻塞/非阻塞
阻塞:Blocking,指 IO 操作需要彻底完成之后才返回到用户空间,调用结果返回之前,调用者被挂起。
非阻塞:Noblocking,指 IO 操作被调用后立即返回给用户一个状态值,无需等到 IO 操作彻底完成,最终的调用结果返回之前,调用者不会被挂起。
2.3 常用的 I/O 模型
阻塞型、非阻塞型、复用型、信号驱动型、异步。
3. 五种常用 I/O 模型
3.1 同步阻塞 I/O 模型
3.1.1 同步阻塞 I/O 模型图示
3.1.2 同步阻塞 I/O 模型说明
① 同步阻塞 I/O 模型是最简单的 I/O 模型,用户线程在内核进行 I/O 操作时被阻塞。
②用户线程通过系统调用 read 发起 I/O 读操作,由用户空间转到内核空间。内核等到数据包到达后,将接收的数据拷贝到用户空间,完成 read 操作。
③ 用户需要等待 read 将数据读取到 Buffer 后,才继续处理接收的数据,整个 I/O 请求的过程中,用户线程是被阻塞的,这导致用户在发起 IO 请求时,不能做任何事情,对 CPU 的资源利用率不高。
3.2 同步非阻塞 I/O 模型
3.2.1 同步非阻塞 I/O 模型图示
3.2.2 同步非阻塞 I/O 模型说明
① 用户线程发起 IO 请求时立即返回,但并未读取到任何数据,用户线程需要不断地发起 IO 请求,直到数据到达后,才真正读取到数据,继续执行,即 “轮询” 机制。
② 整个 IO 请求的过程中,虽然用户线程每次发起 IO 请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的 CPU 资源。
③ 是比较浪费 CPU 的方式,一般很少直接使用这种模型,而是在其他 IO 模型中使用非阻塞这一特性。
3.3 IO 多路复用模型
3.3.1 IO 多路复用模型图示
3.3.2 IO 多路复用模型说明
① 多个连接共用一个等待机制,本模型会阻塞进程,但是进程是阻塞在 select 或者 poll 这两个系统调用上,而不是阻塞在真正的 IO 操作上。
② 用户首先将需要进行 IO 操作添加到 select 中,继续执行其他的工作(异步),同时等待 select 系统调用返回。当数据到达时,IO 被激活,select 函数返回。用户线程正式发起 read 请求,读取数据并继续执行。
③ 从流程上来看,使用 select 函数进行 IO 请求和同步阻塞模型没有太大的区别,甚至还多了添加监控 IO,以及调用 select 函数的额外操作,效率更差,并且阻塞了两次,但是第一次阻塞在 select 上时,select 可以监控多个 IO 上是否已经有 IO 操作准备就绪,即可达到在同一个线程内同时处理多个 IO 请求的目的,而不像阻塞 IO 一次只能监控一个 IO 。
④ 虽然上述方式允许单线程内处理多个 IO 请求,但是每个 IO 请求的过程还是阻塞的(在 select 函数上阻塞),平均时间甚至比同步阻塞 IO 模型还要长,如果用户线程只是注册自己需要的 IO 请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高 CPU 的利用率。
⑤ IO 多路复用是最常用的 IO 模型,但是其异步程度还不够彻底,因为它使用了会阻塞线程的 select 系统调用,因此 IO 多路复用只能称为异步阻塞 IO 模型,而不是真正的异步 IO 模型。
⑥ IO 多路复用是指内核一旦发现进程指定的一个或多个 IO 条件准备读取,就通知该进程。
3.3.3 IO 多路复用模型使用场景
① 当客户端处理多个描述符时(一般是交互式输入和网络套接口),必须使用 IO 复用模型。
② 当一个客户端同时处理多个套接字时(此情况很少出现)。
③ 当一个 TCP 服务器既要处理监听套接字,又要处理已连接套接字,一般也要用到 IO 复用模型。
④ 当一个服务器既要处理监听套接字,又要处理 UDP,一般要使用 IO 复用。
⑤ 当一个服务器要处理多个服务或多个协议,一般要使用 IO 复用。
3.4 信号驱动 IO 模型
3.4.1 信号驱动 IO 模型图示
3.4.2 信号驱动 IO 模型说明
① 信号驱动 IO:Single-driven I/O。
② 用户进程可以通过 sigaction 系统调用注册一个信号处理程序,然后主程序可以继续向下执行,当有 IO 操作准备就绪时,由内核通知触发一个 SIGIO 信号处理程序执行,然后将用户进程所需要的数据从内核空间拷贝到用户空间。
③ 此模型的优势在于等待数据报到达期间进程不被阻塞,用户进程可以继续执行,只要等待来自信号处理函数的通知。
④ 该模型并不常用。
3.5 异步 IO 模型
3.5.1 异步 IO 模型图示
3.5.2 异步 IO 模型说明
① 异步 IO 与信号驱动 IO 最主要的区别是信号驱动 IO 是由内核通知何时可以进行 IO 操作,而异步 IO 则是由内核告诉用户线程 IO 操作何时完成。信号驱动 IO 当内核通知触发信号处理程序时,信号处理程序还需要阻塞在从内核空间缓冲区拷贝数据到用户空间缓冲区这个阶段,而异步 IO 直接是在第二个阶段完成的,内核直接通知用户线程可以进行后续操作了。
② 相比于 IO 多路复用模型,异步 IO 并不十分常用,不少高性能并发服务程序使用 IO 多路复用模型 + 多线程任务处理的架构基本可以满足需求。目前操作系统对异步 IO 的支持并非特别完善,更多的是采用 IO 多路复用模型模拟异步 IO 的方式(IO 事件触发时不直接通知用户线程,而是将数据读写完毕后放到用户指定的缓冲区中)。
4. 五种 I/O 模型图示
5. I/O 模型的具体实现
主要实现方式有如下几种
Select:Linux 实现,对应 IO 复用模型。
Poll:Linux 实现,对应 IO 复用模型。
Epoll:Linux 实现,对应 IO 复用模型,具有信号驱动 IO 模型的某些特性。
Kqueue:FreeBSD 实现,对应 IO 复用模型,具有信号驱动 IO 模型的某些特性。
/dev/poll:Sun 的 Solaris 实现,对应 IO 复用模型,具有信号驱动模型的某些特性。
Iocp:Windows 实现,对应异步 IO 模型。
6. Select/Poll/Epoll 模型对比说明
6.1 对比表格
Select |
Poll |
Epoll |
|
操作方式 |
遍历 |
遍历 |
回调 |
底层实现 |
数组 |
链表 |
哈希表 |
IO 效率 |
每次调用都进行线性遍历 |
每次调用都进行线性遍历 |
事件通知方式,回调 |
最大连接数 |
1024或2048 |
无上限 |
无上限 |
6.2 Selct 模型特点
① POSIX 所规定,目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,本质上是通过设置或者检查存放 FD 标签位的数据结构来进行下一步处理(FD 表示文件描述符)。
② 单个进程可监视的 FD 数量被限制,即能监听端口的数量有限。
cat /proc/sys/fs/file-max
③ 对 socket 是线性扫描,即采用轮询的方法,效率较低。
④ Select 采用了内存拷贝方法来实现内核将 fd 消息通知给用户空间,这样一个用来存放大量 fd 的数据结构,会使得用户空间和内核空间在传递该结构时复制开销大。
6.3 Poll 模型的特点
① 本质上和 select 没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个 fd 对应的设备状态。
② 其没有最大连接数的限制,原因是它是基于链表来存储的。
③ 大量的 fd 的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
④ Poll 的特点是 “水平触发”,如果报告了 fd 后没有被处理,那么下次 poll 时会再次报告该 fd。
6.4 Epool 模型的特点
① 在 Linux 2.6 内核中提出的 select 和 poll 的增强版本。
② 支持水平触发 LT 和边缘触发 ET,最大的特点在于边缘触发,只告诉进程哪些 fd 刚刚变为就绪态,并且只会通知一次。
③ 使用 “事件”的就绪通知方式,通过 epoll_ctl 注册 fd,一旦该 fd 就绪,内核就会采用类似 callback 的回调机制来激活该 fd,epoll_wait 便可以收到通知。
④ 没有最大连接数的限制:能打开的 FD 上线远大于 1024(1G 的内存能监听约 10 万个端口)。
⑤ 效率提升:非轮询的方式,不会随着 FD 数目的增加而效率下降;只有活跃可用的 FD 才会调用 callback 函数,即 epoll 最大的优点在于它只管理活跃的连接,而跟总连接数无关。
⑥ 内存拷贝:利用 MMAP(Memory Mapping)内存映射加速与内核空间的消息传递,即 epoll 使用 MMAP 减少复制开销 。
原文地址:https://www.cnblogs.com/alinuxer/p/9899888.html