原理还是轮询所有文件描述符
该函数建立一个epoll句柄
内核在epoll文件系统中建立了一个 file节点
在内核缓冲区 Cache 中建立一棵红黑树和一条双向链表。
红黑树用来存储以后 epoll_ctl() 注册的文件描述符
双向链表用来存放就绪的文件描述符。
该函数是功能是将被监听的文件描述符添加到epoll句柄,或者从epoll句柄中删除,或者更改文件描述符的监听状态
将对应的文件描述符添加到红黑树中,或者从红黑树中删除,当将文件描述添加到红黑树时,并给内核中断函数注册一条回调函数,告诉内核,如果该文件描述符就绪,将其添加到就绪链表中
该函数返回就绪的文件描述和就绪数目的大小
该函数观察就绪链表中的数据,有数据就返回,没数据就sleep,直到有事件发生或者事件到达
select有最大并发数量限制,默认数量是1024个
select 每次调用需要复制全部的fd,文件描述符集合到内核态,并进行线性遍历。
消耗时间多,且随着文件描述符集合增加,速度越慢,效率延展性差
epoll 没有最大并发数量限制,依赖于系统所能打开的最大文件数目限制
只有已经就绪的文件才会主动触发回调函数,效率和文件数目大小无关,性能延展性好
epoll中的epoll_ctl contol控制
(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内部定义的等待队列)。这也能节省不少的开销。