event中将事件驱动模块,由于支持跨平台,抽象出了event模块。它支持的event类型有:
1、AIO(异步I/O)
2、/dev/poll(solaris和unix特有)
3、epoll(linux2.6特有)
4、eventport(solaris 10特有)
5、kqueue(BSD特有)
6、poll
7、rtsig(实时信号)
8、select
event模块的主要功能就是,监听accept后建立的连接,对读写事件进行添加删除。事件驱动模型和非阻塞I/O模型结合在一起使用。当I/O可读可写的时候,相应的读写事件就会被唤醒,此时会去处理事件的回调函数
对于Linux, nginx大部分event用epoll EPOLLET(边沿触发)的方法来唤醒事件,只有listen端口的读事件是EPOLLLT(水平触发),对于边沿触发,如果出现了可读事件,必须及时处理,否则可能会出现读事件不再触发而没有处理的情况。
event的结构体为:
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 (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
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;
accept锁
当内核accpet一个连接时,会唤醒所有等待的进程,但实际上只有一个进程能获取连接,其它的进程都是无效唤醒的。
nginx的事件处理入口函数为ngx_process_events_and_timers(),其加锁过程为
if (ngx_use_accept_mutex) {
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS;
} else {
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}
在ngx_trylock_accept_mutex里,如果获取到了锁,就将listen的端口读事件加到event处理,等到有新连接到来时就可以accept了
ngx_process_events函数是所有事件处理的入口,它会遍历所有的事件。抢到accept锁的进程与一般进程有些区别,加上了NGX_POST_EVENTS标志表示ngx_process_events只是将事件放到post_events事件的队列中,而不处理。直到ngx_accept_mutex锁去掉后才去处理事件。为什么这样?因为这样做可以减小该进程抢到锁后,从accept开始到结束的时间,以便其它进程继续接收新的连接,提高吞吐量。
ngx_posted_accept_events和ngx_posted_events 分别是accept延迟事件队列和普通延迟事件队列。其实ngx_posted_accept_events还是放在ngx_accept_mutex锁里面处理的,该队列处理的都是accept事件,它会把内核backlog里等待的连接都accept进来,注册到读写事件中。