1.简介
Linux I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数。Linux 2.6内核中有提高网络I/O性能的新方法,即epoll 。
epoll是什么?按照man手册的说法是为处理大批量句柄而作了改进的poll。要使用epoll只需要以下的三个系统函数调用: epoll_create(2),epoll_ctl(2),epoll_wait(2)。
2.select模型的缺陷
(1) 在Linux内核中,select所用到的FD_SET是有限的
内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数:#define __FD_SETSIZE 1024。也就是说,如果想要同时检测1025个句柄的可读状态是不可能用select实现的;或者同时检测1025个句柄的可写状态也是不可能的。
(2) 内核中实现select是使用轮询方法
每次检测都会遍历所有FD_SET中的句柄,显然select函数的执行时间与FD_SET中句柄的个数有一个比例关系,即select要检测的句柄数越多就会越费时
3.Windows IOCP模型的缺陷
windows完成端口实现的AIO,实际上也只是使用内部用线程池实现的,最后的结果是IO有个线程池,你的应用程序也需要一个线程池。很多文档其实已经指出了这引发的线程context-switch所带来的代价。
4.EPOLL模型的优点
(1) 支持一个进程打开大数目的socket描述符(FD)
epoll没有select模型中的限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于select 所支持的2048。下面是我的小PC机上的显示:
pt@Ubuntu:~$ cat /proc/sys/fs/file-max
6815744
那么对于服务器而言,这个数目会更大。
(2) IO效率不随FD数目增加而线性下降
传统select/poll的另一个致命弱点就是当你拥有一个很大的socket集合,由于网络得延时,使得任一时间只有部分的socket是”活跃”的,而select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对”活跃”的socket进行操作:这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。于是,只有”活跃”的socket才会主动去调用callback函数,其他idle状态的socket则不会。在这点上,epoll实现了一个”伪”AIO”,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的,比如一个高速LAN环境,epoll也不比select/poll低多少效率,但若过多使用的调用epoll_ctl,效率稍微有些下降。然而一旦使用idle connections模拟WAN环境,那么epoll的效率就远在select/poll之上了。
(3) 使用mmap加速内核与用户空间的消息传递
无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就显得很重要。在这点上,epoll是通过内核于用户空间mmap同一块内存实现。
5.EPOLL模型的工作模式
(1) LT模式
LT:level triggered,这是缺省的工作方式,同时支持block和no-block socket,在这种模式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
(2) ET模式
LT:edge-triggered,这是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核就通过epoll告诉你,然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作而导致那个文件描述符不再是就绪状态(比如你在发送,接收或是接受请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核就不会发送更多的通知(only once)。不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
6.EPOLL模型的使用方法
epoll用到的所有函数都是在头文件sys/epoll.h中声明的,下面简要说明所用到的数据结构和函数:
(1) epoll_data、epoll_data_t、epoll_event
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件。epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件,可能的取值为:
EPOLLIN: 表示对应的文件描述符可以读;
EPOLLOUT: 表示对应的文件描述符可以写;
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读;
EPOLLERR: 表示对应的文件描述符发生错误;
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 表示对应的文件描述符有事件发生;