• 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可以看这里

  • 相关阅读:
    RHEL 6.5 安装Docker
    sar命令
    Linux 安装部署 Redis
    hugepage设置
    pycharm使用
    oracle如何保证数据一致性和避免脏读
    转:数据库实例自动crash并报ORA-27157、ORA-27300等错误
    oracle安装内核参数设置
    外部表
    LNMP环境搭建
  • 原文地址:https://www.cnblogs.com/sduzh/p/6793879.html
Copyright © 2020-2023  润新知