服务端 I/O:
I/O 在计算机中指 Input/Output, IOPS (Input/Output Per Second)即每秒的输入输出量(或读写次数), 是衡量磁盘性能的主要指标之一. IOPS 是指单位时间内系统能处理的 I/O 请求数量, 一般以每秒处理的 I/O 请求数量为单位, I/O 请求通常为读或写数据操作请求.
一次完整的 I/O 是用户空间的进程数据与内核空间的内核数据的报文的完整交换, 但是由于内核空间与用户空间是严格隔离的, 所以其数据交换过程中不能由用户空间的进程直接调用内核空间的内存数据, 而是需要经历一次从内核空间中的内存数据 copy 到用户空间的进程内存当中, 所以简单说 I/O 就是把数据从内核空间中的内存数据复制到用户空间中进程的内存当中.
而网络通信就是网络协议栈到用户空间进程的 IO 就是网络 IO
磁盘 I/O 是进程向内核发起系统调用, 请求磁盘上的某个资源比如是文件或者是图片, 然后内核通过相应的驱动程序将目标图片加载到内核的内存空间, 加载完成之后把数据从内核内存再复制给进程内存, 如果是比较大的数据也需要等待时间.
每次 IO, 都要经由两个阶段:
将数据从文件先加载至内核内存空间(缓冲区), 等待数据准备完成, 时间较长
将数据从内核缓冲区复制到用户空间的进程的内存中, 时间较短
系统 I/O 模型:
同步 / 异步: 关注的是消息通信机制, 即在等待一件事情的处理结果之前, 被调用者是否提供通知机制.
同步: 进程发出请求调用后, 等待内核返回响应之后再进行下一个请求. 如果内核不进行返回数据, 则进程一直处于等待状态.
异步: 进程发出请求调用后, 不等待内核返回响应, 直接处理下一个请求.(Nginx 是异步处理机制)
阻塞 / 非阻塞: 关注调用者在等待结果返回之前所处的状态.
阻塞: I/O 操作需要彻底完成之后才返回到用户空间, 操作返回之前, 调用者处于被挂起状态, 无法执行其他操作.
非阻塞: I/O 操作被调用后立即返回给用户一个状态值, 无需等待完成, 最终的调用结果返回之前, 调用者不会被挂起, 可以执行其他操作.
网络 I/O 模型
同步阻塞型 I/O 模型(blocking IO)
阻塞 IO 模型是最简单的 IO 模型, 用户线程在内核进行 IO 操作时被阻塞 用户线程通过系统调用 read 发起 IO 读操作, 由用户空间转到内核空间. 内核等到数据包到达后, 然后将接收的数据拷贝到用户空间, 完成 read 操作 用户需要等待 read 将数据读取到 buffer 后, 才继续处理接收的数据. 整个 IO 请求的过程中, 用户线程是被阻塞的, 这导致用户在发起 IO 请求时, 不能做任何事情, 对 CPU 的资源利用率不够 优点: 程序简单, 在阻塞等待数据期间进程 / 线程挂起, 基本不会占用 CPU 资源 缺点: 每个连接需要独立的进程 / 线程单独处理, 当并发请求量大时为了维护程序, 内存, 线程切换开销较大, apache 的 preforck 使用的是这种模式.
简单理解就是: 程序向内核发送 I/O 请求后一直处于等待内核响应的状态, 如果内核处理请求的 I/O 操作不能立即返回, 则进程将一直处于等待状态而不再接收新的请求, 并由进程轮询查看 I/O 是否完成, 完成后进程将 I/O 结果返回给 Client, 在 I/O 没有返回期间进程不能接收其他客户的请求, 而且是由进程自己去看 I/O 是否完成, 这种方式简单, 但是比较慢, 所以用的比较少.
同步非阻塞型 I/O 模型(nonblocking IO)
用户线程发起 IO 请求时立即返回. 但并未读取到任何数据, 用户线程需要不断地发起 IO 请求, 直到数据到达后, 才真正读取到数据, 继续执行. 即 "轮询" 机制存在两个问题: 如果有大量文件描述符都要等, 那么就得一个一个的 read. 这会带来大量的 Context Switch(read 是系统调用, 每调用一次就得在用户态和核心态切换一次). 轮询的时间不好把握. 这里是要猜多久之后数据才能到. 等待时间设的太长, 程序响应延迟就过大; 设的太短, 就会造成过于频繁的重试, 干耗 CPU 而已, 是比较浪费 CPU 的方式, 一般很少直接使用这种模型, 而是在其他 IO 模型中使用非阻塞 IO 这一特性.
简单理解就是: 程序向内核发送 I/O 请求后一直等待内核响应, 如果内核处理请求的 I/O 操作不能理解返回 I/O 结果, 进程将不再等待, 继续处理其他请求, 但是仍然需要进程每隔一段时间就要查看内核 I/O 是否完成. 这是一种比较浪费 CPU 的一种方式: 轮询的时间不好把握, 如果设置的等待时间过长, 程序响应延迟就过大; 设置的时间过短, 就会造成过于频繁的重试.
I/O 多路复用型(IO multiplexing)
I/O 多路复用型就是我们说的 select,poll,epoll, 有些地方也称这种 IO 方式为 event driven IO.select/epoll 的好处就在于单个 process 就可以同时处理多个网络连接的 IO. 它的基本原理就是 select,poll,epoll 这个 function 会不断的轮询所负责的所有 socket, 当某个 socket 有数据到达了, 就通知用户进程. 当用户进程调用了 select, 那么整个进程会被 block, 而同时, kernel 会 "监视" 所有 select 负责的 socket, 当任何一个 socket 中的数据准备好了, select 就会返回. 这个时候用户进程再调用 read 操作, 将数据从 kernel 拷贝到用户进程. 比如: Apache prefork 是此模式的 select,work 是 poll 模式.
简单理解即: I/O 多路复用机制可以同时监控多个描述符, 当某个描述符就绪(读或者写就绪), 则立即通知相应程序进行读或者写操作. 但 select,poll,epoll 本质上都是同步 I/O, 因为他们都需要在读写事件就绪后自己负责进行读写, 也就是说这个读写过程是阻塞的.
信号驱动式 IO(signal-driven IO)
信号驱动 IO 就是用户进程可以通过 sigaction 系统调用注册一个信号处理程序, 然后主程序可以继续向下执行, 当有 IO 操作准备就绪时, 由内核通知触发一个 SIGIO 信号处理程序执行, 然后将用户进程所需要的数据从内核空间拷贝到用户空间 此模型的优势在于等待数据报到达期间进程不被阻塞. 用户主程序可以继续执行, 只要等待来自信号处理函数的通知.
优点: 线程并没有在等待数据时被阻塞, 内核直接返回调用接收信号, 不影响进程继续处理其他请求因此可以提高资源的利用率
缺点: 信号 I/O 在大量 IO 操作时可能会因为信号队列溢出导致没法通知
简单理解就是: 程序进程通过在内核上调用 sigaction 函数, 向内核发送 I/O 调用后, 不用等待内核响应, 可以继续接受其他请求, 内核收到进程请求后进行的 I/O 如果不能立即返回, 就由内核等待结果, 直到 I/O 完成后内核再通知进程. apache event 就是这个模式.
异步(非阻塞)IO(asynchronous IO)
相对于同步 IO, 异步 IO 不是顺序执行. 用户进程进行 aio_read 系统调用之后, 无论内核数据是否准备好, 都会直接返回给用户进程, 然后用户态进程可以去做别的事情. 等到 socket 数据准备好了, 内核直接复制数据给进程, 然后从内核向进程发送通知. IO 两个阶段, 进程都是非阻塞的. Linux 提供了 AIO 库函数实现异步, 但是用的很少. 目前有很多开源的异步 IO 库, 例如 libevent,libev,libuv.
程序进程向内核发送 IO 调用后, 不用等待内核响应, 可以继续接受其他强求, 内核调用的 IO 如果不能立即返回, 内核会继续处理其他事物, 知道 IO 完成后将结果通知给内核, 内核再将 IO 完成的结果返回给进程, 期间进程可以接受新的请求, 内核也可以处理新的事物, 因此相互之间是不影响的. 可以实现较大的同时并实现较高的 IO 复用, 因此异步非阻塞是使用最多的一种通信方式.
nginx 事件驱动模型实现方式
Nginx 支持在多种不同的操作系统实现不同的事件驱动模型, 但是其在不同的操作系统甚至是不同的系统版本上面的实现方式不尽相同, 主要有以下实现方式
select:
select 库是在 Linux 和 Windows 平台都基本支持的 事件驱动模型库, 并且在接口的定义也基本相同, 只是部分参数的含义略有差异, 最大并发限制 1024, 是最早期的事件驱动模型.
poll:
在 Linux 的基本驱动模型, Windows 不支持此驱动模型, 是 select 的升级版, 取消了最大的并发限制, 在编译 nginx 的时候可以使用 --with-poll_module 和 --without-poll_module 这两个指定是否编译 select 库.
epoll:
epoll 是库是 Nginx 服务器支持的最高性能的事件驱动库之一, 是公认的非常优秀的事件驱动模型, 它和 select 和 poll 有很大的区别, epoll 是 poll 的升级版, 但是与 poll 的效率有很大的区别.
epoll 的处理方式是创建一个待处理的事件列表, 然后把这个列表发给内核, 返回的时候在去轮训检查这个表, 以判断事件是否发生, epoll 支持一个进程打开的最大事件描述符的上限是系统可以打开的文件的最大数, 同时 epoll 库的 IO 效率不随描述符数目增加而线性下降, 因为它只会对内核上报的 "活跃" 的描述符进行操作.
rtsig:
不是一个常用事件驱动, 最大队列 1024, 不是很常用
kqueue:
用于支持 BSD 系列平台的高校事件驱动模型, 主要用在 FreeBSD 4.1 及以上版本, OpenBSD 2.0 级以上版本, NetBSD 级以上版本及 Mac OS X 平台上, 该模型也是 poll 库的变种, 因此和 epoll 没有本质上的区别, 都是通过避免轮训操作提供效率.
/dev/poll:
用于支持 unix 衍生平台的高效事件驱动模型, 主要在 Solaris 平台, HP/UX, 该模型是 sun 公司在开发 Solaris 系列平台的时候提出的用于完成事件驱动机制的方案, 它使用了虚拟的 / dev/poll 设备, 开发人员将要见识的文件描述符加入这个设备, 然后通过 ioctl()调用来获取事件通知, 因此运行在以上系列平台的时候请使用 / dev/poll 事件驱动机制.
eventport:
该方案也是 sun 公司在开发 Solaris 的时候提出的事件驱动库, 只是 Solaris 10 以上的版本, 该驱动库看防止内核崩溃等情况的发生.
Iocp:
Windows 系统上的实现方式, 对应第 5 种 (异步 I/O) 模型.
常用模型汇总
来源: http://www.bubuko.com/infodetail-3076424.html