标签:
前段时间听了boss栋的nio的讲座,有很多没有理解的地方,最近特意又研究了一下,写一下心得。
计算机中io分为网络io以及磁盘io,我们这里主要说的就是网络io。
http://blog.csdn.net/zhxue123/article/details/22285957中提到:
一般来说,服务器端的I/O主要有两种情况:一是来自网络的I/O;二是对文件(设备)的I/O。Windows的异步I/O模型能很好的适用于这两种情况。而Linux针对前者提供了epoll模型,针对后者提供了AIO模型(关于是否把两者统一起来争论了很久)。
epoll跟AIO的区别?
http://www.cnblogs.com/Anker/p/3265058.html 中介绍poll流程时,提到:
__pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒了。
网络io与磁盘io又有共同点?
以上是两个疑问,搜了以下AIO,原来是异步io
Linux 2.6内核包含对一套新的异步IO,即AIO 的支持为用户空间提供统一的异步I/O 接口。目前好像只支持本地IO。有时间再学习。
io模型分为五种,阻塞io,非阻塞io,io复用,信号驱动io,异步io
而nio就是实现的io复用模型
操作系统实现IO复用模型,一般通过3个系统调用poll,select和epoll,其中poll和select实现相似,在这里统一讲解。epoll实现略有差别,我们单独讲解,在目前的JDK实现中,优先选择epoll实现。
select/poll实现原理图
Select/poll在系统调用时一般需要3个参数:
待检查的文件描述符
待检查的文件描述符对应的事件
超时时间
操作系统会逐个检查待检查的文件(Sock是Socket在内核中的数据结构),是否有事件就绪。每个文件也有一个poll方法,主要是检查自己的某个事件是否准备就绪。我们也用一个简化的模型来分析,每个文件有一个事件和状态的对应关系,当事件就绪就设置此状态。同时每个事件还有一个等待队列。
当事件状态是就绪状态,返回就绪。
否则创建一个内部对象放入等待队列,这个内部对象有一个回调方法,用来唤醒当前进程。
如果有事件就绪返回就绪文件的个数。
如果所有的文件都没有事件就绪,则阻塞进程,阻塞时间为timeout。
在阻塞期间,如果设备通过中断促使某些事件变为就绪,则对事件对应的等待列表进行回调唤醒阻塞的进程。
epoll原理图
由于poll和select系统调用会把所有的文件当作参数传递给操作系统,每次调用会产生大量用户空间到内核空间(内核空间到用户空间)的拷贝。而且由于每次对所有的文件进行遍历,如果存在大量不活跃的文件导致性能的急剧下降。所以提出了一个新的系统调用epoll。
它通过3个系统调用来完成:
poll_create:会创建一个文件,其实是创建了在内核的数据结构,在Linux中一切设备都是文件,epoll可以理解为一个虚拟设备。
poll_ctl:向epoll中加入/删除/修改待检测的文件描述符。当文件描述符加入待检测列表时,epoll会向此文件注册回调函数,在文件某事件就绪时会回调此函数。函数的默认实现是把此文件加入事件就绪列表中。
poll_wait:检查是否有就绪的文件描述符。直接检查就绪列表,如果有数据就返回就绪个数,否则返回空。
由于epoll是通过事件通知的方式获取就绪列表的,所以它只关注活跃的文件,这样对于大量不活跃文件的监控有更好的效率,但由于它实现的复杂性,当所检测的文件大多是活跃的情况,它的性能有可能低于select或poll。
个人理解,当文件活跃时,会调用回调函数,如果绝大部分都活跃,意味着都要调用回调函数,这个时候跟poll中轮询文件效率差不多甚至还要差
具体介绍,还可以参考 http://www.cnblogs.com/Anker/p/3265058.html
其中介绍poll流程时,有一段话
遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll)
由poll的原理图,可以看出,每一个Socket对应一个Sock(Sock是Socket在内核中的数据结构),我理解为poll epoll中常被提到的文件描述符,poll遍历每个fd时,需要调用每个fd对应的poll方法(每个文件也有一个poll方法),主要是检查自己的某个事件是否准备就绪。
总结一下select/poll与epoll
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
另外,知乎上的讨论帖,对于拓宽考虑nio问题的视野很有帮助 如 http://www.zhihu.com/question/32163005中提到以下一些观点
1、
标签:
原文地址:http://www.cnblogs.com/govoid/p/5557660.html