epoll的实现原理(2)
笔记,内容翻译自:
The Implementation of epoll(3)
The Implementation of epoll(4)
回调函数 ep_poll_callback()
前面提到的ep_insert()
函数将epoll实例附加到监视文件描述符fd的等待队列,注册ep_poll_callback()
为队列唤醒的回调函数。下面剖析一下这个回调函数:
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
//↑ pwq->wait
int pwake = 0;
unsigned long flags;
struct epitem *epi = ep_item_from_wait(wait); //通过strcut eppoll_entry找到epitem
struct eventpoll *ep = epi->ep; //进一步找到eventpoll
接下来,使用自旋锁锁定eventpoll
spin_lock_irqsave(&ep->lock, flags);
**然后检查事件是否是用户让epoll监视的。**前面ep_insert()
将注册事件为~0
,有两个原因:
1.用户可能频繁改变需要监视的事件,但是重新注册poll
回调效率不高。
2.其次,并非所有的事件都遵循系统的事件掩码设置,因此完全依靠系统的设置不太可靠。
if (key && !((unsigned long) key & epi->event.events))
goto out_unlock;
如果没有监控任何事件,跳到解锁out_unlock
接下来检查epoll实例是否正尝试将事件传输到用户态(也就是在调用ep_send_events_proc()
时)。
如果是的话,将当前epitem
加入到一个链表头在 struct eventpoll
中的单链表里。代码如下:
if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
//正在传输为NULL(默认状态是EP_UNACTIVE_PTR)
if (epi->next == EP_UNACTIVE_PTR) {
epi->next = ep->ovflist; //前面已经取得了ep的锁
ep->ovflist = epi;
if (epi->ws) {
__pm_stay_awake(ep->ws);
}
}
goto out_unlock; //完成后跳到解锁
}
不是的话,接下来ep_poll_callback()
检查当前的struct epitem
是否已经在就绪队列中。
在用户没有没有机会调用epoll_wait()
时可能发生这种情况(以前加入过了,用户还没机会处理)。
如果不在的话,函数会将struct epitem
加入到就绪队列(struct eventpoll
的成员rdllist
)中。
if (!ep_is_linked(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
ep_pm_stay_awake_rcu(epi);
}
然后 ep_poll_callback()
唤醒等待wq
和poll_wait
的进程。
wq
用于在超时时间(timeout)没到时,用户正在通过epoll_wait()
等待events的时候(最多唤醒一个进程)。
poll_wait
是epoll实现的文件系统的poll()
,请记住epoll同样是一个可轮询的文件描述符。