Dav*_*rtz 96
关于这一点有很多错误信息,但真正的原因是:
典型的服务器可能正在处理200个连接.它将为需要写入或读取数据的每个连接提供服务,然后需要等待更多的工作要做.当它在等待时,如果在这200个连接中的任何一个上接收到数据,则需要中断它.
有了select
,内核必须将进程添加到200个等待列表,每个连接一个.要做到这一点,它需要一个"thunk"将进程附加到等待列表.当进程最终唤醒时,需要从所有200个等待列表中删除它,并且需要释放所有这些thunk.
相比之下,同epoll
时,epoll
插座本身有一个等待列表.只需要使用一个thunk,就只需要将该进程放在一个等待列表中.当进程唤醒时,需要仅从一个等待列表中删除它,并且只需要释放一个thunk.
需要明确的是,随着epoll
时,epoll
本身具有插座附连到每个那些200个连接.但是,对于每个连接,当它首先被接受时,这样做一次.对于每个连接,当它被移除时,这会被拆除一次.相反,对每个select
块的调用必须将进程添加到每个被监视的套接字的等待队列中.
具有讽刺意味的是,select
最大的成本来自于检查没有活动的插座是否有任何活动.有了epoll
,没有必要检查没有活动的套接字,因为如果它们确实有活动,它们会epoll
在活动发生时通知套接字.从某种意义上说,select
每次调用时轮询每个套接字,select
以查看是否存在任何活动,同时epoll
使套接字活动本身通知进程.
小智 20
之间的主要区别epoll
和select
是在select()
文件描述符的名单要等上只存在一个单一的时间select()
调用,调用任务只停留在插座等待队列单个呼叫的持续时间.在epoll
,在另一方面,创建能够从多个其他文件描述符要等待事件的单一文件描述符,所以监控FD的名单是持久的,并且任务留在跨多个系统调用插座等待队列.此外,由于epoll
fd可以在多个任务之间共享,因此它不再是等待队列上的单个任务,而是一个本身包含另一个等待队列的结构,其中包含当前在epoll
fd 上等待的所有进程.(在实现方面,这是由套接字的等待队列抽象的,它们持有一个函数指针和一个void*
传递给该函数的数据指针).
所以,再解释一下这些机制:
epoll
文件描述符有私家struct eventpoll
是跟踪哪些FD的连接到这个FD.struct eventpoll
还有一个等待队列,可以跟踪当前epoll_wait
在这个fd上的所有进程.struct epoll
还有一个当前可用于读取或写入的所有文件描述符的列表.epoll
使用文件描述符添加到fd时epoll_ctl()
,将该fd的等待队列epoll
添加struct eventpoll
到该fd.它还检查fd当前是否准备好进行处理,并将其添加到就绪列表中,如果是的话.epoll
fd使用时epoll_wait
,内核首先检查就绪列表,如果任何文件描述符已经准备就立即返回.如果没有,它会将自己添加到内部的单个等待队列中struct eventpoll
,然后进入休眠状态.epoll()
编辑的套接字上发生事件时,它会调用epoll
回调,该回调会将文件描述符添加到就绪列表中,并唤醒当前正在等待的任何服务器struct eventpoll
.显然,需要对struct eventpoll
各种列表和等待队列进行大量细致的锁定,但这是一个实现细节.
需要注意的重要一点是,上面没有任何一点我描述了一个循环遍历所有感兴趣的文件描述符的步骤.通过完全基于事件并使用一组持久的fd和一个就绪列表,epoll可以避免花费O(n)时间进行操作,其中n是被监视的文件描述符的数量.