Linux内核笔记:epoll实现原理

Posted sduzh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核笔记:epoll实现原理相关的知识,希望对你有一定的参考价值。

在通过epoll_ctl(2)向epoll中添加被监视文件描述符时,会将ep_poll_callback()作为回调函数添加被监视文件的等待队列中。下面分析ep_poll_callback()函数

1004 static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
1005 {
1006         int pwake = 0;
1007         unsigned long flags;
1008         struct epitem *epi = ep_item_from_wait(wait);
1009         struct eventpoll *ep = epi->ep;
1010         int ewake = 0;

1008行首先调用ep_item_from_wait()来获取到与被监视文件描述符相关联的结构体struct epitem,获取方法就是利用container_of宏。

1009行再根据struct epitem的ep字段获取到代表epoll对象实例的结构体struct eventpoll。

1012         if ((unsigned long)key & POLLFREE) {
1013                 ep_pwq_from_wait(wait)->whead = NULL;
1014                 /*
1015                  * whead = NULL above can race with ep_remove_wait_queue()
1016                  * which can do another remove_wait_queue() after us, so we
1017                  * can‘t use __remove_wait_queue(). whead->lock is held by
1018                  * the caller.
1019                  */
1020                 list_del_init(&wait->task_list);
1021         }

判断返回的事件掩码里是否设置了标志位POLLFREE(什么时候会设置该标志?),如果是则将当前等待对象从文件描述符的等待队列中删除(疑问:注释是什么意思?为什么不需要加锁?)。

接下来对epoll的实例加锁:

1023         spin_lock_irqsave(&ep->lock, flags);

接下来判断epitem中的事件掩码是不是并没有包括任何poll(2)事件,如果是的话,则解锁后直接返回:

1025         /*
1026          * If the event mask does not contain any poll(2) event, we consider the
1027          * descriptor to be disabled. This condition is likely the effect of the
1028          * EPOLLONESHOT bit that disables the descriptor when an event is received,
1029          * until the next EPOLL_CTL_MOD will be issued.
1030          */
1031         if (!(epi->event.events & ~EP_PRIVATE_BITS))
1032                 goto out_unlock;

什么时候会出现上述情况呢?注释里也说了,就是在设置了EPOLLONESHOT标志的时候。对EPOLLONESHOT标志的处理是在epoll_wait()的返回过程,调用ep_send_events_proc()的时候,如果设置了EPOLLONESHOT标志则将EP_PRIVATE_BITS以外的标志位全部清0:

1552                         if (epi->event.events & EPOLLONESHOT)
1553                                 epi->event.events &= EP_PRIVATE_BITS;

接下来判断返回的事件里是否有用户真正感兴趣的事件,没有则解锁后返回,否则继续。

1034         /*
1035          * Check the events coming with the callback. At this stage, not
1036          * every device reports the events in the "key" parameter of the
1037          * callback. We need to be able to handle both cases here, hence the
1038          * test for "key" != NULL before the event match test.
1039          */
1040         if (key && !((unsigned long) key & epi->event.events))
1041                 goto out_unlock;  

如果此时就绪链表rdllist没有被其他进程访问,则直接将当前文件描述符添加到rdllist链表中,否则的话添加到ovflist链表中。ovflist默认值是EP_UNACTIVE_PTR,epoll_wait()遍历rdllist之前会把ovflist设置为NULL,遍历完再恢复为EP_UNACTIVE_PTR,因此通过判断ovflist的值是不是EP_UNACTIVE_PTR可知此时rdllist是不是正在被访问。

1049         if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
1050                 if (epi->next == EP_UNACTIVE_PTR) {
1051                         epi->next = ep->ovflist;
1052                         ep->ovflist = epi;
1053                         if (epi->ws) {
1054                                 /*
1055                                  * Activate ep->ws since epi->ws may get
1056                                  * deactivated at any time.
1057                                  */
1058                                 __pm_stay_awake(ep->ws);
1059                         }
1060 
1061                 }
1062                 goto out_unlock;
1063         }
1064 
1065         /* If this file is already in the ready list we exit soon */
1066         if (!ep_is_linked(&epi->rdllink)) {
1067                 list_add_tail(&epi->rdllink, &ep->rdllist);
1068                 ep_pm_stay_awake_rcu(epi);
1069         }

如果是描述符是添加到ovflist链表中,说明此时已经有ep_wait()准备返回了,因此不用再唤醒epoll实例的等待队列,因此1062行直接跳到解锁处;否则的话,则唤醒因为调用epoll_wait()而等待在epoll实例等待队列上的进程(这里最多只会唤醒一个进程):

1075         if (waitqueue_active(&ep->wq)) {
1076                 if ((epi->event.events & EPOLLEXCLUSIVE) &&
1077                                         !((unsigned long)key & POLLFREE)) {
1078                         switch ((unsigned long)key & EPOLLINOUT_BITS) {
1079                         case POLLIN:
1080                                 if (epi->event.events & POLLIN)
1081                                         ewake = 1;
1082                                 break;
1083                         case POLLOUT:
1084                                 if (epi->event.events & POLLOUT)
1085                                         ewake = 1;
1086                                 break;
1087                         case 0:
1088                                 ewake = 1;
1089                                 break;
1090                         }
1091                 }
1092                 wake_up_locked(&ep->wq);
1093         }

如果epoll实例的poll队列非空,也会唤醒等待在poll队列上的进程,不过是在解锁后才会进行唤醒操作。

1094         if (waitqueue_active(&ep->poll_wait))
1095                 pwake++;

最后解锁并返回:

1097 out_unlock:
1098         spin_unlock_irqrestore(&ep->lock, flags);
1099 
1100         /* We have to call this outside the lock */
1101         if (pwake)
1102                 ep_poll_safewake(&ep->poll_wait);
1103 
1104         if (epi->event.events & EPOLLEXCLUSIVE)
1105                 return ewake;
1106 
1107         return 1;

注意到ep_poll_callback()的返回值和EPOLLEXCLUSIVE标志有关,该标志是用来处理这种情况:当多个进程中的不同epoll实例在监视同一个文件描述符时,如果该文件描述符上有事件发生,则所有的epoll实例所在进程都将被唤醒,这样有可能造成“惊群”(thundering herd)。关于EPOLLEXCLUSIVE可以看这里

以上是关于Linux内核笔记:epoll实现原理的主要内容,如果未能解决你的问题,请参考以下文章

linux网络编程 - epoll边沿触发/水平触发内核实现代码分析

linux网络编程 - epoll边沿触发/水平触发内核实现代码分析

linux网络编程epoll内核实现代码分析

linux网络编程 - epoll内核实现代码分析

Linux系统I/O模型及select、poll、epoll原理和应用

epoll为啥这么快?epoll的实现原理是啥?