参考资料<深入理解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 }
同理,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 }
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);
下图展示了该方法的流程