# Nginx EPOLL 事件模块 action 回调原理

前面我们详细描述了各个模块之间的交互流程,本节我们将详细分析 EPOLL 事件模块如何处理事件。我们先来看如下结构,很简单,我们也看到过:ngx_event_actions_t 结构中将保存用于完成对应事件的函数指针,而 EPOLL 模块正是实现了这些函数指针来完成相应动作,我们只需要看看 EPOLL 如何 完成这些 函数指针的实现即可。

typedef struct {
    ngx_str_t              *name;
    void                 *(*create_conf)(ngx_cycle_t *cycle);
    char                 *(*init_conf)(ngx_cycle_t *cycle, void *conf);
    ngx_event_actions_t     actions;
} ngx_event_module_t;typedef struct {
    ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);ngx_int_t  (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    ngx_int_t  (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);ngx_int_t  (*add_conn)(ngx_connection_t *c);
    ngx_int_t  (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);ngx_int_t  (*notify)(ngx_event_handler_pt handler);ngx_int_t  (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
                                 ngx_uint_t flags);ngx_int_t  (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
    void       (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;// 进程 全局变量 表示当前多路复用器的回调函数变量
ngx_event_actions_t   ngx_event_actions;// 用于操作多路复用器的宏定义,可以很明显看到:直接回调其中注册的函数,这里便是 epoll 模块的实现
#define ngx_process_events   ngx_event_actions.process_events
#define ngx_done_events      ngx_event_actions.done
#define ngx_add_event        ngx_event_actions.add
#define ngx_del_event        ngx_event_actions.del
#define ngx_add_conn         ngx_event_actions.add_conn
#define ngx_del_conn         ngx_event_actions.del_conn#define ngx_notify           ngx_event_actions.notify
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

我们看到过,EPOLL 模块实现了如下 回调函数:

static ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */
    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
#if (NGX_HAVE_EVENTFD)
        ngx_epoll_notify,                /* trigger a notify */
#else
        NULL,                            /* trigger a notify */
#endif
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# ngx_epoll_init 函数

该函数我们看到将在 event core 模块中的 ngx_event_process_init 方法中回调,而 event core 模块 的ngx_event_process_init 方法,将会在 event core 模块 在 worker进程 启动时回调。

//  event core 模块 回调 epoll 模块的 ngx_epoll_init  函数
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle){
    ...
    for (m = 0; cycle->modules[m]; m++) {
        if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
            continue;
        }
        if (cycle->modules[m]->ctx_index != ecf->use) {
            continue;
        }
        module = cycle->modules[m]->ctx;
        if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
            exit(2);
        }
        break;
    }
    ...
}// worker 进程创建成功后调用 event core 模块 的 ngx_event_process_init  方法
static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker){
    ...
    for (i = 0; cycle->modules[i]; i++) {
        if (cycle->modules[i]->init_process) {
            if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
                exit(2);
            }
        }
    }
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

接下来我们直接来看 ngx_epoll_init 方法的实现。我们看到该方法前面也描述过,为每个进程 创建自己的 epoll fd,每个worker 进程使用该 epoll fd 来管理自己的事件。

static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer){
    ngx_epoll_conf_t  *epcf;
​
    epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);if (ep == -1) {
        ep = epoll_create(cycle->connection_n / 2);if (ep == -1) {
            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
                          "epoll_create() failed");
            return NGX_ERROR;
        }#if (NGX_HAVE_EVENTFD) // 初始化 event fd
        if (ngx_epoll_notify_init(cycle->log) != NGX_OK) {
            ngx_epoll_module_ctx.actions.notify = NULL;
        }
#endif#if (NGX_HAVE_FILE_AIO) // 初始化 AIO 相关
        ngx_epoll_aio_init(cycle, epcf);
#endif#if (NGX_HAVE_EPOLLRDHUP) // 测试epoll 是否支持 2.6.7 以上 的 EPOLLRDHUP 事件(用于检测 连接的对端 断开连接)
        ngx_epoll_test_rdhup(cycle);
#endif
    }
    if (nevents < epcf->events) { // 初始化事件列表
        if (event_list) {
            ngx_free(event_list);
        }
        event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
                               cycle->log);
        if (event_list == NULL) {
            return NGX_ERROR;
        }
    }
    nevents = epcf->events; // 总事件数
    ngx_io = ngx_os_io;
    ngx_event_actions = ngx_epoll_module_ctx.actions;
#if (NGX_HAVE_CLEAR_EVENT) // 配置使用 EPOLL 的 LT 或者 ET 模式,默认 LT 水平触发模式
    ngx_event_flags = NGX_USE_CLEAR_EVENT
#else
    ngx_event_flags = NGX_USE_LEVEL_EVENT
#endif
                      |NGX_USE_GREEDY_EVENT
                      |NGX_USE_EPOLL_EVENT;
    return NGX_OK;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# ngx_epoll_add_event 函数

该函数将用于向 EPOLL 中注册事件。接下来我们来看该函数的实现。源码相对简单:根据添加事件的类型,调用 (epoll_ctl(ep, op, c->fd, &ee) 系统调用,将事件添加到epoll的监听列表中,我们看到这里使用 struct epoll_event 的 data 数据域 来保存与之关联的 ngx_connection_t 结构。源码如下。

static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
{
    int                  op;
    uint32_t             events, prev;
    ngx_event_t         *e;
    ngx_connection_t    *c;
    struct epoll_event   ee;
    c = ev->data;
    events = (uint32_t) event;
    if (event == NGX_READ_EVENT) { //  注册读事件(事件类型:EPOLLIN)
        e = c->write;
        prev = EPOLLOUT;
#if (NGX_READ_EVENT != EPOLLIN|EPOLLRDHUP)
        events = EPOLLIN|EPOLLRDHUP;
#endif
    } else { // 注册写事件(事件类型:EPOLLOUT)
        e = c->read;
        prev = EPOLLIN|EPOLLRDHUP;
#if (NGX_WRITE_EVENT != EPOLLOUT)
        events = EPOLLOUT;
#endif
    }
    if (e->active) { // 之前已经注册过,此时 e active 有效位置位,那么表示使用 EPOLL_CTL_MOD 标志位,表示修改 epoll 监听的事件类型
        op = EPOLL_CTL_MOD;
        events |= prev;
    } else { // 否则为添加操作
        op = EPOLL_CTL_ADD;
    }#if (NGX_HAVE_EPOLLEXCLUSIVE && NGX_HAVE_EPOLLRDHUP) // 配置 且 OS 支持 多进程监听 同一个 socket 并由OS 自身维护互斥,那么检测 NGX_EXCLUSIVE_EVENT 标志位,若存在,那么不需要监听 对端关闭的事件 其中 ( #define NGX_EXCLUSIVE_EVENT  EPOLLEXCLUSIVE)
    if (flags & NGX_EXCLUSIVE_EVENT) {
        events &= ~EPOLLRDHUP;
    }
#endif
    ee.events = events | (uint32_t) flags;
    ee.data.ptr = (void *) ((uintptr_t) c | ev->instance); // 将 ngx_connection_t 与 instance 位关联(其中 : unsigned         instance:1  占用一个字节,也即低位一字节,用于表示 事件是否已经过期)
    if (epoll_ctl(ep, op, c->fd, &ee) == -1) { // 标准的 epoll CRUD  接口
        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
                      "epoll_ctl(%d, %d) failed", op, c->fd);
        return NGX_ERROR;
    }
    ev->active = 1; // 添加操作,添加成功后,此时 ngx_event_t 有效
    return NGX_OK;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# ngx_epoll_del_event 函数

该函数用于从 epoll 监听列表中删除指定的事件。我们看到该函数若在 ngx_event_t 有效时(active变量为1)那么等价于修改监听标志位 操作(通常我们可以将标志位设置为 epoll 不存在的监听类型,此时等效于 屏蔽指定 fd 的 事件通知),否则 等价于 删除操作。源码如下。

static ngx_int_t ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
{
    int                  op;
    uint32_t             prev;
    ngx_event_t         *e;
    ngx_connection_t    *c;
    struct epoll_event   ee;
    if (flags & NGX_CLOSE_EVENT) { // 表示此时文件描述符 fd 已经关闭,此时 epoll 会自动将其从监听列表中移除,我们不需要操作 epoll 接口
        ev->active = 0;
        return NGX_OK;
    }
    c = ev->data; // 获取与之绑定的 ngx_connection_t 结构
    if (event == NGX_READ_EVENT) { // 注册的事件类型为读事件
        e = c->write;
        prev = EPOLLOUT;
    } else { // 注册的事件类型为写事件
        e = c->read;
        prev = EPOLLIN|EPOLLRDHUP; // 用于打印日志,这里无视就好,我已经删除了 日志打印 代码
    }
    if (e->active) { // 若此时 ngx_event_t 仍然有效,那么该函数将等效于 修改 监听标志位
        op = EPOLL_CTL_MOD;
        ee.events = prev | (uint32_t) flags;
        ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
    } else { // 否则 执行 删除操作,将 fd 从 epoll 中删除
        op = EPOLL_CTL_DEL;
        ee.events = 0;
        ee.data.ptr = NULL;
    }
    if (epoll_ctl(ep, op, c->fd, &ee) == -1) { 
        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
                      "epoll_ctl(%d, %d) failed", op, c->fd);
        return NGX_ERROR;
    }
    ev->active = 0; // 该函数执行完毕后,将 事件 设置为 失效状态
    return NGX_OK;
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# ngx_epoll_add_connection 函数

该函数直接用于向 epoll 中添加新的连接,所以比较直接,不判断是否存在,直接指定 EPOLL_CTL_ADD 添加新连接。注意此时将会监听 新注册 的 fd 的 所有事件:读事件(EPOLLIN)、写事件(EPOLLOUT)、触发方式(EPOLLET指定边缘触发)、对端关闭连接事件(EPOLLRDHUP)。此时很明显看到若调用该函数,默认触发方式为 边缘触发!。

static ngx_int_t ngx_epoll_add_connection(ngx_connection_t *c)
{
    struct epoll_event  ee;
    ee.events = EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP; // 边缘触发
    ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);
    if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      "epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);
        return NGX_ERROR;
    }
    // 读写事件 都生效
    c->read->active = 1; 
    c->write->active = 1;
    return NGX_OK;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# ngx_epoll_del_connection 函数

该函数用于将 监听的 fd 直接从 epoll 监听列表中删除,此时将不再判断 ngx_event_t 是否有效,因为此时 于 ngx_event_t 结构无关。源码如下。

pass:其实我们不难发现,操作 ngx_connection_t 为上层结构,而 ngx_event_t 嵌入在 ngx_connection_t 中,上面操作 ngx_event_t 的函数,将会根据 ngx_event_t 事件来完成具体动作,而这里的 ngx_epoll_add_connection 函数 和 ngx_epoll_del_connection 函数的 ngx_connection_t 操作将无视所有,直接关闭或者添加~

static ngx_int_t ngx_epoll_del_connection(ngx_connection_t *c, ngx_uint_t flags)
{
    int                 op;
    struct epoll_event  ee;
    if (flags & NGX_CLOSE_EVENT) {
        c->read->active = 0;
        c->write->active = 0;
        return NGX_OK;
    }
    op = EPOLL_CTL_DEL;
    ee.events = 0;
    ee.data.ptr = NULL;
    if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      "epoll_ctl(%d, %d) failed", op, c->fd);
        return NGX_ERROR;
    }
    // 删除后读写事件均无效
    c->read->active = 0;
    c->write->active = 0;
    return NGX_OK;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# ngx_epoll_notify 函数

该函数用于唤醒调用 epoll_wait 等待事件的 EPOLL 进程,我们可以看到这里直接使用之前创建的 notify_fd 来通知,此时由于写入了数据,当再次调用 epoll_wait 时,由于存在该 notify_fd 存在 写事件,将直接返回,不会阻塞。

#if (NGX_HAVE_EVENTFD) // 只有存在 EPOLL FD 才能使用该函数
static ngx_int_t ngx_epoll_notify(ngx_event_handler_pt handler)
{
    static uint64_t inc = 1;
    notify_event.data = handler;
    if ((size_t) write(notify_fd, &inc, sizeof(uint64_t)) != sizeof(uint64_t)) {
        ngx_log_error(NGX_LOG_ALERT, notify_event.log, ngx_errno,
                      "write() to eventfd %d failed", notify_fd);
        return NGX_ERROR;
    }
    return NGX_OK;
}
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13

# ngx_epoll_process_events 函数

该函数用于处理 epoll 中注册的事件。我们知道由于定时器的存在,所以不可能让 epoll_wait 一直等待下去,所以具体的等待事件的时间需要根据最少等待时间的定时器 ngx_msec_t timer 来决定,所以在 epoll_wait(ep, event_list, (int) nevents, timer)的时间将为 timer,那么此时就会存在两种情况:

1、超时了还没有事件:检测无异常退出

2、未超时存在事件:循环处理事件即可(若指定延迟处理,那么挂入队列。若未指定,那么立即调用 handler )

// 三大变量分别用于表示:epoll fd、接收准备好事件的数组、事件数
static int                  ep = -1;
static struct epoll_event  *event_list;
static ngx_uint_t           nevents;
​
​
static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
    int                events;
    uint32_t           revents;
    ngx_int_t          instance, i;
    ngx_uint_t         level;
    ngx_err_t          err;
    ngx_event_t       *rev, *wev;
    ngx_queue_t       *queue;
    ngx_connection_t  *c;
    events = epoll_wait(ep, event_list, (int) nevents, timer); // 等待事件发生或者超时,全局变量 
    err = (events == -1) ? ngx_errno : 0; // 检测错误
    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { // 若指定等待后更新缓存的时间结构,那么进行更新(时间模块我们后面再单独用一篇文章来描述)
        ngx_time_update();
    }
    if (err) { // 若发生异常,那么打印日志
        if (err == NGX_EINTR) {
            if (ngx_event_timer_alarm) {
                ngx_event_timer_alarm = 0;
                return NGX_OK;
            }
            level = NGX_LOG_INFO;
        } else {
            level = NGX_LOG_ALERT;
        }
        ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
        return NGX_ERROR;
    }if (events == 0) { // 第一种情况:定时器超时,且未发生任何事件,那么直接返回(若指定定时器不存在,但居然没有任何事件产生 epoll 竟然返回了,这种异常情况必定打印 错误日志)
        if (timer != NGX_TIMER_INFINITE) {
            return NGX_OK;
        }
        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                      "epoll_wait() returned no events without timeout");
        return NGX_ERROR;
    }
    // 第二种情况,定时器未超时且存在事件,那么遍历处理即可
    for (i = 0; i < events; i++) {
        c = event_list[i].data.ptr; // 获取之前保存的 ngx_connection_t 结构
        instance = (uintptr_t) c & 1; // 检测是否有效
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1); // 将指针最后一位保存是否有效位移出,还原真正的 ngx_connection_t 指针
        rev = c->read; // 获取读事件
        if (c->fd == -1 || rev->instance != instance) { // 此时表示 nginx 已经修改了当前连接,那么此时准备好的事件的 fd 为过期的fd,那么打印日志,直接返回
            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "epoll: stale event %p", c);
            continue;
        }
        revents = event_list[i].events; // 获取当前 监听的fd 准备好的事件标志位
        if (revents & (EPOLLERR|EPOLLHUP)) { // 若当前 监听的fd 发生了错误事件,那么在事件类型上添加 EPOLLIN 和 EPOLLOUT,让后面的事件处理器 来处理这种错误状态,否则,将没有回调的事件处理器来处理该状态(我就想问下:为毛不注册一个 单独处理 这种错误事件的处理器呢??读者也可以思考下?这是完全可以的)
            revents |= EPOLLIN|EPOLLOUT;
        }
        if ((revents & EPOLLIN) && rev->active) { // 处理读事件
#if (NGX_HAVE_EPOLLRDHUP) 
            if (revents & EPOLLRDHUP) { // 对端关闭事件设置 pending_eof 标志位
                rev->pending_eof = 1;
            }
#endif
            rev->ready = 1; // 标志读事件已经准备好
            rev->available = -1;
            if (flags & NGX_POST_EVENTS) { // 若当前指定需要暂时将事件挂到 事件列表中,那么这里将不会回调 注册的事件时指定的 handler 函数,仅仅添加到对应列表即可(接受连接事件,那么 添加到 ngx_posted_accept_events队列,其他事件 : ngx_posted_events队列)
                queue = rev->accept ? &ngx_posted_accept_events
                                    : &ngx_posted_events;
                ngx_post_event(rev, queue); // 普通的 add 添加队列操作
            } else { // 回调注册的函数
                rev->handler(rev);
            }
        }
        // 处理写事件
        wev = c->write;
        if ((revents & EPOLLOUT) && wev->active) { // 同读事件一样的处理方式
            if (c->fd == -1 || wev->instance != instance) {
                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                               "epoll: stale event %p", c);
                continue;
            }
            wev->ready = 1; // 标志写事件已经准备好
            if (flags & NGX_POST_EVENTS) { // 同读事件一样 
                ngx_post_event(wev, &ngx_posted_events);} else {
                wev->handler(wev);
            }
        }
    }
    return NGX_OK;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

那么,此时来看以下代码是否熟悉:

void ngx_single_process_cycle(ngx_cycle_t *cycle){
    ...
    for ( ;; ) { // 循环处理所有事件,知道 nginx 停止
        ngx_process_events_and_timers(cycle); // 本循环的核心在此,所有注意力集中到该方法
    }
    ...
}void ngx_process_events_and_timers(ngx_cycle_t *cycle) {
    ...
    if (ngx_timer_resolution) { // 不执行时间事件,那么设置timer 为 -1( #define NGX_TIMER_INFINITE  (ngx_msec_t) -1 )
        timer = NGX_TIMER_INFINITE;
        flags = 0;
    } else { // 否则从保存时间事件的红黑树中找到 最近需要执行的定时器 timer
        timer = ngx_event_find_timer();
        flags = NGX_UPDATE_TIME; // 设置标志位为需要更新时间
    }    
    
    if (ngx_use_accept_mutex) { // 当设置需要使用 接收互斥锁时,尝试获取锁(防止多进程条件下 epoll 监听server fd 导致惊群现象发生)
        if (ngx_accept_disabled > 0) { // 用于多进程处理连接事件的负载均衡(当前进程的该值大于0,那么不处理连接事件,后面再说)
            ngx_accept_disabled--;
        } else { // 否则尝试获取互斥锁
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) { // 获取锁发生异常
                return;
            }
            // 获取锁未发生异常,那么可能:获得锁或者未获得锁
            
            if (ngx_accept_mutex_held) { // 进程持有互斥锁那么可以执行接收事件(可以看到,这里指定 flags 为 NGX_POST_EVENTS的意义: 延迟处理所有事件,意义何在?快速返回,不会长时间持有锁操作 ,好好体会)
                flags |= NGX_POST_EVENTS;
            } else { // 未持有互斥锁,那么当不执行时间时间时或者待执行的时间事件的执行时间大于 ngx_accept_mutex_delay 延迟时间,那么将定时器设置为 ngx_accept_mutex_delay(默认 500 ms)
                if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
                {
                    timer = ngx_accept_mutex_delay;
                }
            }
        }
    }
    (void) ngx_process_events(cycle, timer, flags); // 调用 #define ngx_process_events 此时很明显调用了epoll 的 ngx_epoll_process_events
    ...
    ngx_event_process_posted(cycle, &ngx_posted_accept_events); // 立即处理 accept 事件队列
    if (ngx_accept_mutex_held) { // 然后【作者:黄俊,微信:bx_java】立即释放锁(为何不先释放锁再执行 accept 队列?一看 epoll 就理解不深入,若释放了 其他进程同样也能检测到同样的事件为何?因为当前进程还没有处理该事件)
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }
    // 释放锁后再处理 定时器和其他读写事件,此时保证了快速高效的减少锁范围,因为 对于当前进程的 epoll 来说,自身的读写事件 与 其他进程无关,所以处理完 accept 后 应该释放锁再干别的!
    ngx_event_expire_timers();
    ngx_event_process_posted(cycle, &ngx_posted_events);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# ngx_epoll_done 函数

该函数为 epoll 模块的清理操作,可以看到:将所有创建的 fd 和 事件列表全部删除。

static void ngx_epoll_done(ngx_cycle_t *cycle){
    if (close(ep) == -1) { // 关闭 epoll
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "epoll close() failed");
    }
    ep = -1;
    
#if (NGX_HAVE_EVENTFD) //  关闭用于通知的 event fd
    if (close(notify_fd) == -1) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "eventfd close() failed");
    }
    notify_fd = -1;
#endif#if (NGX_HAVE_FILE_AIO) // 关闭 aio
    if (ngx_eventfd != -1) {
        if (io_destroy(ngx_aio_ctx) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "io_destroy() failed");
        }
        if (close(ngx_eventfd) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "eventfd close() failed");
        }
        ngx_eventfd = -1;
    }
    ngx_aio_ctx = 0;
#endif
    ngx_free(event_list); // 删除事件列表
    event_list = NULL;
    nevents = 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33