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边沿触发/水平触发内核实现代码分析