epoll惊群原因分析

63 篇文章 0 订阅
31 篇文章 0 订阅

转自:https://www.cnblogs.com/sduzh/p/6810469.html

考虑如下情况(实际一般不会做,这里只是举个例子):

  1. 在主线程中创建一个socket、绑定到本地端口并监听
  2. 在主线程中创建一个epoll实例(epoll_create(2))
  3. 将监听socket添加到epoll中(epoll_ctl(2))
  4. 创建多个子线程,每个子线程都共享步骤2里创建的同一个epoll文件描述符,然后调用epoll_wait(2)等待事件到来accept(2)
  5. 请求到来,新连接建立

这里的问题就是,在第5步的时候,会有多少个线程被唤醒而从epoll_wait()调用返回?答案是不一定,可能只有一个,也可能有部分,也可能是全部。当然在多个线程都唤醒的情况下,只会有一个线程accept()调用会成功。

为何如此?从内核代码分析,原因如下:

在调用epoll_wait(2)的时候,设置的epoll的等待队列回调函数是default_wake_function,添加队列的时候调用的是__add_wait_queue_exclusive()。

ep_poll_callback()中唤醒操作调用的是wake_up_locked(&ep->wq),最终会调用__wake_up_common,后者会判断exclusive标志:

1

2

3

4

5

6

7

8

9

10

11

12

13

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,

            int nr_exclusive, int wake_flags, void *key)

{

    wait_queue_t *curr, *next;

 

    list_for_each_entry_safe(curr, next, &q->task_list, task_list) {

        unsigned flags = curr->flags;

 

        if (curr->func(curr, mode, wake_flags, key) &&

                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)

            break;

    }

}

因为__wake_up_common()的调用是从wake_up_locked()开始的,__wake_up_common的各个参数值为:

  • q: struct eventpoll.wq
  • mode: TASK_NORMAL
  • nr_exclusive:1
  • wake_flags: 0
  • key:NULL。

局部变量curr的值可以通过epoll_wait()的源码得到,具体为:

  • curr->flags: WQ_FLAG_EXCLUSIVE
  • curr->func: default_wake_function

default_wake_function调用的是try_to_wake_up。而try_to_wake_up只有在要唤醒的进程状态不是TASK_NORMAL时才会返回0,TASK_NORMAL的定义是(TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)。

因此__wake_up_common里的if条件会在第一次判断的时候就满足,唤醒一个进程后便返回了,那为什么实际测试会发现有多个进程被唤醒呢?

原因就在于这个唯一被唤醒的进程。

当某个等待在epoll实例上的进程被唤醒后,最终会进入到ep_scan_ready_list() 这个函数中,ep_scan_ready_list()会以回调方式调用ep_send_events_proc()来将数据复制到用户空间。而ep_scan_ready_list()函数在返回之前会再次判断epoll的就绪链表rdllist是否为空,如果不为空的话,就会再唤醒其他进程!下面就是ep_scan_ready_list()返回之前的判断操作:

1

2

3

4

5

6

7

8

9

10

if (!list_empty(&ep->rdllist)) {

    /*

     * Wake up (if active) both the eventpoll wait list and

     * the ->poll() wait list (delayed after we release the lock).

     */

    if (waitqueue_active(&ep->wq))

        wake_up_locked(&ep->wq);

    if (waitqueue_active(&ep->poll_wait))

        pwake++;

}

而在水平触发方式下,从就绪链表中移出来的文件描述符,如果当前仍有事件就绪(可读、可写等),会在复制到用户空间后被再次添加到就绪链表中:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

if (epi->event.events & EPOLLONESHOT)

    epi->event.events &= EP_PRIVATE_BITS;

else if (!(epi->event.events & EPOLLET)) {

    /*

     * If this file has been added with Level

     * Trigger mode, we need to insert back inside

     * the ready list, so that the next call to

     * epoll_wait() will check again the events

     * availability. At this point, no one can insert

     * into ep->rdllist besides us. The epoll_ctl()

     * callers are locked out by

     * ep_scan_ready_list() holding "mtx" and the

     * poll callback will queue them in ep->ovflist.

     */

    list_add_tail(&epi->rdllink, &ep->rdllist);

    ep_pm_stay_awake(epi);

}

因此在水平触发模式下,被唤醒的进程又会去唤醒其他进程,除非当前事件已经被处理完或者所有进程都已经被唤醒(被唤醒的进程会从epoll等待队列上移除)。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值