五种IO模型详解( 三 )


(3).直到kernel buffer中数据准备完成 , 再去轮询时不再返回EWOULDBLOCK , 而是将httpd阻塞 , 以等待数据复制到app buffer 。
(4).httpd在到阶段不被阻塞 , 但是会不断去发送read()轮询 。 在被阻塞 , 将cpu交给内核把数据copy到app buffer 。
如下图:
五种IO模型详解文章插图
2.3 I/O Multiplexing模型称为多路IO模型或IO复用 , 意思是可以检查多个IO等待的状态 。 有三种IO复用模型:select、poll和epoll 。 其实它们都是一种函数 , 用于监控指定文件描述符的数据是否就绪 。
就绪指的是对某个系统调用不再阻塞了 , 可以直接执行IO 。 例如对于read()来说 , 数据准备好了就是就绪状态 , 此时read()可以直接去读取数据且能立即读取到数据 , 对write()来说 , 就是有空间可以写入数据了(比如缓冲区未满) , 此时write()可以直接写入 。
就绪种类包括是否可读、是否可写以及是否异常 , 其中可读条件中就包括了数据是否准备好 , 也即数据是否已经在kernel buffer中 。 当就绪之后 , 将通知进程 , 进程再发送对数据操作的系统调用 , 如read() 。
所以 , 这三个函数仅仅只是处理了数据是否准备好以及如何通知进程的问题 。 可以将这几个函数结合阻塞和非阻塞IO模式使用 , 但通常IO复用都会结合非阻塞IO模式 。
select()和poll()差不多 , 它们的监控和通知手段是类似的 , 只不过poll()要更聪明一点 , 某些时候效率也更高些 , 此处仅以select()监控单个文件请求为例简单介绍IO复用 , 至于更具体的、监控多个文件以及epoll的方式 , 在本文的最后专门解释 。
(1).当想要加载某个文件时 , 假如httpd要发起read()系统调用 , 如果是阻塞或者非阻塞情形 , 那么read()会根据数据是否准备好而决定是否返回 。 是否可以主动去监控这个数据是否准备到了kernel buffer中呢 , 亦或者是否可以监控send buffer中是否有新数据进入呢?这就是select()/poll()/epoll的作用 。
(2).当使用select()时 , 进程被select()所『阻塞』 , 之所以阻塞要加上引号 , 是因为select()有时间间隔选项可用控制阻塞时长 , 如果该选项设置为0 , 则select不阻塞而是立即返回 , 还可以设置为永久阻塞 。
(3).当select()的监控对象就绪时 , httpd进程通过轮询判断知道可以执行read()了 , 于是httpd再发起read()系统调用 , 此时数据会从kernel buffer复制到app buffer中并read()成功 。
(4).httpd发起read()系统调用后切换到内核 , 由内核占用CPU来复制数据到app buffer , 所以httpd进程被阻塞 。
上面的描述可能还太过抽象 , 这里用shell伪代码来简单描述select()的工作方式(细节并非准确 , 但易于理解) 。 假设有一个select命令 , 作用和select()函数相同 。 伪代码如下:
# select监控指定的文件描述符 , 并返回已就绪的描述符数量给x # 进程将阻塞在select命令上 , 直到select返回 x=$(select fd1 fd2 fd3)# 如果x大于0 , 说明有文件描述符数据就绪 , 于是遍历所有fd ,# 并分别使用read去读取这些fd , 但并不知道具体是哪个fd已 # 就绪 , 所以read时最好是非阻塞的读取 , 否则read每一个未 # 就绪的fd时都会阻塞 if [ x -gt 0 ];thenfor fd in fd1 fd2 fd3;doread -t 0 -u $fd# read操作最好是非阻塞的done fi所以 , 在使用IO复用模型时 , 真正的IO操作(比如read)最好是非阻塞方式的 , 但并非必须 。 比如只监控一个文件描述符时 , select能返回意味着这个文件描述符一定是就绪的(select还有其它返回值 , 但这里不考虑其它返回值 。
IO多路复用时 , 模型如图:
五种IO模型详解文章插图


推荐阅读