首页 > 技术文章 > Nginx:事件模块

runnyu 2015-10-27 17:32 原文

参考资料<深入理解Nginx>

 

根据不同的系统内核,Nginx会使用不同的事件驱动机制,本次描述的场景是使用epoll来驱动事件的处理。

 

 

epoll的使用方法

1.int epoll_create(int size);

epoll_create返回一个句柄,之后epoll的使用将依靠这个句柄来标识。参数size只是告诉epoll所要处理的大致事件数目,一些内核版本的实现中,这个参数没有任何意义。

2.int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);

epoll_ctl向epoll对象中添加、修改或者删除感兴趣的事件,返回0表示成功,否则返回-1。

参数epfd是epoll_create方法返回的句柄,而op参数的意义如下表

参数fd是待检测的连接套接字,第四个参数是在告诉epoll对什么样的事件感兴趣,它使用的epoll_event结构体定义如下

struct epoll_event {
    _uint32_t events;
    epoll_data_t data;
};

   events取值如下表

  而data成员是一个epoll_data联合

typedef union epoll_data {
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

这个data成员还与具体的使用方式相关。例如,ngx_epoll_module模块使用了给ptr成员,作为指向ngx_connection_t连接的指针。

3.int epoll_wait(int fd,struct epoll_event *events,int maxevents,int timeout);

   第一个参数epfd是epoll的描述符。第二个参数events则是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中,

   第三个参数表示本次可以返回的最大事件数目,第四个参数表示在没有检测到时间发生时最多等待的时间。

 

ngx_epoll_module模块

该模块使用如下的上下文结构

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 */
        NULL,                            /* process the changes */
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};

在Nginx的启动过程中。ngx_epoll_init方法将会被调用,它主要做了两件事情:

1.调用epoll_create方法创建epoll对象。

2.创建event_list数组,用于进行epoll_wait调用时传递内核态的事件。

 

ngx_epoll_add_event通过调用epoll_ctl向epoll中添加或者删除事件。可以通过这个函数的部分源码来了解一下该过程

 1 static ngx_int_t
 2 ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
 3 {
 4     int                  op;
 5     uint32_t             events, prev;
 6     ngx_event_t         *e;
 7     ngx_connection_t    *c;
 8     struct epoll_event   ee;
 9 
10     //每个事件的data成员都存放着其对应的ngx_connection_t连接
11     c = ev->data;
12 
13     //确定当前时间是读事件还是写事件
14     events = (uint32_t) event;
15 
16     ...
17     //根据active标志位确定是否为活跃事件,以决定到底是修改还是添加事件
18     if (e->active) {
19         op = EPOLL_CTL_MOD;
20         events |= prev;
21 
22     } else {
23         op = EPOLL_CTL_ADD;
24     }
25 
26     ee.events = events | (uint32_t) flags;
27     ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
28 
29     //调用epoll_ctl方法向epoll中添加事件
30     if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
31         ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
32                       "epoll_ctl(%d, %d) failed", op, c->fd);
33         return NGX_ERROR;
34     }
35 
36     //标识为活跃事件
37     ev->active = 1;
38 
39     return NGX_OK;
40 }
View Code

同理,ngx_epoll_del_event方法也通过类似的方式删除事件。

对于ngx_epoll_add_connection和ngx_epoll_del_connection方法,也是调用epoll_ctl进行处理,只是每一个连接都对应读/写事件。

 

ngx_epoll_process_events方法则调用epoll_wait来获取事件并且处理事件,下面是该函数的部分源码

 1 static ngx_int_t
 2 ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
 3 {
 4     int                       events;
 5     uint32_t               revents;
 6     ngx_int_t             instance, i;
 7     ngx_event_t         *rev, *wev, **queue;
 8     ngx_connection_t  *c;
 9 
10 
11     //一开始就是等待事件,最长等待时间为timer
12     events = epoll_wait(ep, event_list, (int) nevents, timer);
13     
14     ...
15     //循环开始处理收到的所有事件
16     for (i = 0; i < events; i++) {
17         //ptr成员指向的是ngx_connection_t,但最后一位有特殊含义,需要将它屏蔽掉
18         c = event_list[i].data.ptr;
19         instance = (uintptr_t) c & 1;
20         c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
21         
22         //取出读事件
23         rev = c->read;
24         ...
25         //取得发生一个事件
26         revents = event_list[i].events;
27 
28         ...
29         //该事件是一个读事件而且是活跃的
30         if ((revents & EPOLLIN) && rev->active) {
31 
32             ...
33             //如果设置了NGX_POST_EVENTS标识,事件放入到相应的队列中
34             if (flags & NGX_POST_EVENTS) {
35                 queue = (ngx_event_t **) (rev->accept ?
36                                &ngx_posted_accept_events : &ngx_posted_events);
37 
38                 ngx_locked_post_event(rev, queue);
39             //否则立刻处理
40             } else {
41                 rev->handler(rev);
42             }
43         }
44       
45         //获取写事件
46         wev = c->write;
47 
48         if ((revents & EPOLLOUT) && wev->active) {
49             
50             ...
51             if (flags & NGX_POST_EVENTS) {
52                 ngx_locked_post_event(wev, &ngx_posted_events);
53 
54             } else {
55                 wev->handler(wev);
56             }
57         }
58     }
59     ...
60     return NGX_OK;
61 }
View Code

 

 

ngx_process_events_and_timers流程

在Nginx中,每个worker进程都在ngx_worker_process_cycle方法中循环处理事件。

处理分发事件实际上就是调用的ngx_process_events_and_timers方法,循环调用该函数就是在处理所有的事件,该方法的核心的操作主要有以下3个:

1.调用所使用的事件驱动模块实现的process_events方法,处理网络事件(调用ngx_epoll_process_events方法)。

2.处理两个post事件队列中的事件(调用ngx_event_process_posted方法)。

3.处理定时器时间(调用ngx_event_expire_timers方法)

下图展示了该方法的流程,结合前面的进行理解

事实上,在调用上面循环的方法之前ngx_event_core_module模块会做一些初始化工作:

1.预分配cycle->connection数组充当连接池

2.预分配所有写事件到cycle->read_events数组

3.预分配所有读事件到cycle->write_events数组

(Nginx中连接池跟读写事件都是这个阶段预先分配好的,其大小跟配置项有关)

4.为所有ngx_listening_t监听对象中的connectin成员分配连接,同时对监听端口的读事件设置处理方法为ngx_event_accept(最后有这个方法的介绍)

5.将监听对象连接的读事件添加到事件驱动模块中,这样,epoll等事件模块就开始检测监听服务,并开始向用户提供服务了。 

 

建立新连接

处理新连接事件的回调函数是ngx_event_accept,其原型如下

void ngx_event_accept(ngx_event_t *ev);

下图展示了该方法的流程

推荐阅读