五种IO模型详解( 三 )


(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模型详解文章插图
select/poll的性能问题
select()/poll()的性能会随着监控的文件描述符数量增多而快速下降 。 其原因是多方面的 。
其中一个原因是它们所监控的文件描述符会以某种数据结构全部传送给内核 , 让内核负责监控这些文件描述符 , 当内核发现某个文件描述符已经就绪时 , 会修改数据结构 , 然后将修改后的数据结构传回给进程 。 所以涉及了两次数据传输的过程 。
对于select()来说 , 每次传递的数据结构的大小是固定的 , 都是1024个描述符的大小 。 对于poll()来说 , 只会传递被监控的文件描述符 , 所以文件描述符少的时候 , poll()的性能是可以的 , 此外poll()可以超出1024个文件描述符的监控数量限制 , 但随着描述符数量的增多 , 来回传递的数据量也是非常大的 。
基于这方面的性能考虑 , 更建议使用信号驱动IO或epoll模型 , 它们都是直接告诉内核要监控哪些文件描述符 , 内核会以合适的数据结构安排这些待监控的文件描述符(如epoll , 内核采用红黑树的方式) , 换句话说 , 它们不会传递一大片的文件描述符数据结构 , 效率更高 。
使用IO复用还有很多细节 , 本文在此仅只是对其作最基本的功能性描述 , 在本文末还会多做一些扩展 , 至于更多的内容需自行了解 。
2.4 Signal-driven I/O模型即信号驱动IO模型 。 当文件描述符上设置了O_ASYNC标记时 , 就表示该文件描述符是信号驱动的IO 。
注:可能你觉得O_ASYNC应该表示的是异步IO , 但并非如此 。
在历史上 , 信号驱动IO也称为异步IO , 比如这个标记就暗含了这一点历史 。
如今常说的术语【异步】 , 是由POSIX AIO规范所提供的功能 , 这个异步表示某进程发起IO操作时 , 立即返回 , 当IO完成或有错误时 , 该进程会收到通知 , 于是该进程可以去处理这个通知 , 比如执行回调函数 。
当某个文件描述符使用信号驱动IO模型时 , 要求进程配置信号SIGIO的信号处理程序 , 然后进程就可以做其他任何事情 。 当该文件描述符就绪时 , 内核会向该进程发送SIGIO信号 。 该进程收到SIGIO信号后 , 就会去执行已经配置号的信号处理程序 。
通常来说 , SIGIO的信号处理程序中会编写read()类的读取代码 , 这表示在收到SIGIO时在信号处理程序中执行read操作 , 另一种常见的作法是在SIGIO的信号处理程序中设置某变量标记 , 然后在外部判断该标记是否为true , 如果标记为true , 则执行read类的操作 。
使用Shell伪代码如下:
# 第一种模式:在信号处理程序中执行IO操作 trap 'read...' SIGIO# 设置SIGIO的信号处理程序 # 然后就可以执行其它任意任务 # 当在执行其它任务过程中 , 内核发送了SIGIO , 进程会 # 立即去执行SIGIO信号处理程序 ...other codes... ? # 第二种模式:在信号处理程序中设置变量标记 , 在外部执行IO操作 trap 'a=1' SIGIO ... other codes... #while [ $a -eq 1 ];doread... done