select poll epoll IO复用模型详解

  之前程序中使用的IO函数都是同步的,无论阻塞式还是非阻塞式,在数据从内核拷贝到用户空间过程,用户线程都是被阻塞的。非阻塞IO只是当内核还没准备好数据时立即返回不等待,需要用户自己去不断检查内核数据是否准备好,依然不高效。IO多路复用提出了新的思路,将IO过程分为等待内核数据准备好和读取/写入内核两部分。一个IO函数监控多个IO可读/可写事件,任意1个IO设备准备好时返回(需要代码中轮询查看是哪个IO文件描述符,什么事件),再调用对应的read/write函数操作,减少不必要的等待时间,高效了很多。具体的实现有select、poll和epoll三种


select IO模型

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。

它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作

select对于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。

select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描。这也浪费了很大有一部分开销。


poll IO模型

poll 与select 的本质是一样的,同样包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大,但是它没有最大连接数的限制,原因是它是基于链表来存储的。

select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)


epoll IO模型

epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发。

epoll只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。

另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

epoll优点:

1.文件描述符数目没有上限,通过epoll_ctl()来注册一个文件描述符,内核中使用红黑树的数据结构来管理所有需要监控的文件描述符。

2.基于事件就绪通知方式,一旦被监听的某个文件描述符就绪,内核会采用类似于callback的回调机制,迅速激活这个文件描述符,这样随着文件描述符数量的增加,也不会影响判定就绪的性能

3.维护就绪队列:当文件描述符就绪,就会被放到内核中的一个就绪队列中,这样调用epoll_weit获取就绪文件描述符的时候,只要取队列中的元素即可。

你可能感兴趣的:(Nginx)