《深入理解Nginx 模块开发与架构解析》笔记之epoll事件模块
来源:互联网 发布:电脑视频播放器 知乎 编辑:程序博客网 时间:2024/05/17 09:26
epoll
Linux epoll
Linux epoll工作模式
- 水平触发LT
- 边缘触发ET
前者是默认的,可以处理阻塞和非阻塞套接字,后者只能处理非阻塞套接字。
Linux epoll原理
如上图所示,Linux中的epoll简单来说就是这样,用一个红黑树来存放所有epoll关心的事件,用一个双向链表来存放当前被激活的事件。
如何使用epoll
- epoll_create系统调用
int epoll_create(int size);
epoll_create返回一个句柄,之后epoll的使用都靠这个句柄。size是告诉大致处理的事件数目,在新的Linux内核中这个参数没有意义。
- epoll_ctl系统调用
int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
向epoll对象添加、修改或删除事件。具体由第二个参数而定。epfd是epoll_create返回的句柄,op参数:
第三个参数fd是待监测的连接套接字,第四个参数结构:
struct epoll_event{ __uint32_t events; epoll_data_t data;};
data成员是一个epoll_data联合体:
typedef union epoll_data{ void *ptr; int fd; uint32_t u32; uint64_t u64;} epoll_data_t;
在ngx_epoll_module模块中只使用了ptr成员,指向ngx_connection_t。
- epoll_wait系统调用
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeou);
收集epoll监控的事件中已经发生的事件,如果没有任何一个发生,则最多等待timeout毫秒后返回。
epfd是epoll描述句柄。
events是分配好的epoll_event结构体数组,epoll会把发生的事件复制到events中。
maxevents是本次可以返回的最大事件数目。
timeout是等到的毫秒数。
Nginx_epoll_module
ngx_epoll_commands配置项数组
static ngx_command_t ngx_epoll_commands[] = { /* 这个配置项表示调用一次epoll_wait最多可以返回的事件数 */ { ngx_string("epoll_events"), NGX_EVENT_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, 0, offsetof(ngx_epoll_conf_t, events), NULL }, /* 指明在开启异步I/O且使用io_setup系统调用初始化异步I/O上下文环境时,初试分配I/O事件个数 */ { ngx_string("worker_aio_requests"), NGX_EVENT_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, 0, offsetof(ngx_epoll_conf_t, aio_requests), NULL }, ngx_null_command};
ngx_epoll_conf_t配置项结构体
typedef struct { ngx_uint_t events; ngx_uint_t aio_requests;} ngx_epoll_conf_t;
events是调用epoll_wait是传入的maxevents;epoll_wait的第二个参数events数组的大小也是有它来决定的。
ngx_epoll_module_ctx实现事件模块接口
static ngx_str_t epoll_name = ngx_string("epoll");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 */ }};
ngx_epoll_init
static ngx_int_tngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer){ ngx_epoll_conf_t *epcf; /* 获取create_conf中生成的ngx_epoll_conf_t结构体,它已经被赋予解析完 配置文件后的值 */ epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module); if (ep == -1) { /* 调用epoll_create创建epoll对象,这个size参数无关紧要 */ 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_FILE_AIO) /* 异步I/O */ ngx_epoll_aio_init(cycle, epcf);#endif } if (nevents < epcf->events) { if (event_list) { ngx_free(event_list); } /* 分配event_list数组,大小为配置项epoll_events的参数events */ event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events, cycle->log); if (event_list == NULL) { return NGX_ERROR; } } //数组个数 nevents = epcf->events; //指明读写I/O的函数 ngx_io = ngx_os_io; //设置ngx_event_actions接口 ngx_event_actions = ngx_epoll_module_ctx.actions;#if (NGX_HAVE_CLEAR_EVENT) /* NGX_USR_CLEAR_EVENT宏设置ET模式epoll */ 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;}
ngx_epoll_add_event
从上面epoll实现事件接口可以看出,enable和add接口都是使用ngx_epoll_add_event函数的。
static ngx_int_tngx_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; /* 事件的data成员存放着ngx_connection_t连接 */ c = ev->data; /* 根据event参数确定是读事件还是写事件 */ events = (uint32_t) event; if (event == NGX_READ_EVENT) { e = c->write; prev = EPOLLOUT;#if (NGX_READ_EVENT != EPOLLIN|EPOLLRDHUP) events = EPOLLIN|EPOLLRDHUP;#endif } else { e = c->read; prev = EPOLLIN|EPOLLRDHUP;#if (NGX_WRITE_EVENT != EPOLLOUT) events = EPOLLOUT;#endif } /* 根据active标志位确定事件是否活跃,以此决定是修改还是添加事件 */ if (e->active) { op = EPOLL_CTL_MOD; events |= prev; } else { op = EPOLL_CTL_ADD; } ee.events = events | (uint32_t) flags; /* ptr成员储存的是ngx_connection_t连接 */ ee.data.ptr = (void *) ((uintptr_t) c | ev->instance); ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0, "epoll add event: fd:%d op:%d ev:%08XD", c->fd, op, ee.events); /* 调用epoll_ctl函数向epoll中添加事件或者在epoll中修改事件 */ 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 = 1;#if 0 ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;#endif return NGX_OK;}
ngx_epoll_del_event
ngx_epoll_del_event和ngx_epoll_add_event是一样的。
static ngx_int_tngx_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; /* * when the file descriptor is closed, the epoll automatically deletes * it from its queue, so we do not need to delete explicitly the event * before the closing the file descriptor */ /* 设置事件关闭 */ if (flags & NGX_CLOSE_EVENT) { ev->active = 0; return NGX_OK; } //获得ngx_connection_t连接 c = ev->data; /* 判断event参数的意图 */ if (event == NGX_READ_EVENT) { e = c->write; prev = EPOLLOUT; } else { e = c->read; prev = EPOLLIN|EPOLLRDHUP; } /* 判断事件是否是活跃的 */ if (e->active) { op = EPOLL_CTL_MOD; ee.events = prev | (uint32_t) flags; ee.data.ptr = (void *) ((uintptr_t) c | ev->instance); } else { op = EPOLL_CTL_DEL; ee.events = 0; ee.data.ptr = NULL; } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0, "epoll del event: fd:%d op:%d ev:%08XD", c->fd, op, ee.events); //调用epol_ctl函数 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;}
ngx_epoll_process_events
这个函数实现了epoll对事件的收集、分发。
static ngx_int_tngx_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, **queue; ngx_connection_t *c; /* NGX_TIMER_INFINITE == INFTIM */ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "epoll timer: %M", timer); /* 调用epoll_wait来获取当前已准备的事件 */ 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(); } /* epoll_wait出错 */ 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; } /* epoll_wait没有获取到事件 */ if (events == 0) { 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; } /* 上锁,处理事件 */ ngx_mutex_lock(ngx_posted_events_mutex); /* 遍历所有准备就绪事件 */ for (i = 0; i < events; i++) { /* 获取当前事件的ngx_connection_t连接 */ c = event_list[i].data.ptr; /* 这里有个有趣的地方,ngx_connection_t的最后一位总是为0,所以Nginx 就拿它来做instance标志位,判断事件是否过时 */ instance = (uintptr_t) c & 1; c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1); /* 获取读事件 */ rev = c->read; // 事件是否过时 if (c->fd == -1 || rev->instance != instance) { /* * the stale event from a file descriptor * that was just closed in this iteration */ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "epoll: stale event %p", c); continue; } /* 取出事件类型,数组中的每一个元素都是epoll_event结构体 */ revents = event_list[i].events; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "epoll: fd:%d ev:%04XD d:%p", c->fd, revents, event_list[i].data.ptr); if (revents & (EPOLLERR|EPOLLHUP)) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "epoll_wait() error on fd:%d ev:%04XD", c->fd, revents); }#if 0 if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) { ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "strange epoll_wait() events fd:%d ev:%04XD", c->fd, revents); }#endif if ((revents & (EPOLLERR|EPOLLHUP)) && (revents & (EPOLLIN|EPOLLOUT)) == 0) { /* * if the error events were returned without EPOLLIN or EPOLLOUT, * then add these flags to handle the events at least in one * active handler */ revents |= EPOLLIN|EPOLLOUT; } /* 是否是读事件、是否活跃 */ if ((revents & EPOLLIN) && rev->active) {#if (NGX_HAVE_EPOLLRDHUP) if (revents & EPOLLRDHUP) { rev->pending_eof = 1; }#endif if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) { rev->posted_ready = 1; } else { rev->ready = 1; } //flags中含有NGX_POST_EVENTS表示这批事件需要延后处理 if (flags & NGX_POST_EVENTS) { //加入到延迟操作的队列中 queue = (ngx_event_t **) (rev->accept ? &ngx_posted_accept_events : &ngx_posted_events); ngx_locked_post_event(rev, queue); } else { //无需延后,直接处理 rev->handler(rev); } } /* 取出写事件 */ wev = c->write; if ((revents & EPOLLOUT) && wev->active) { // 事件是否过期 if (c->fd == -1 || wev->instance != instance) { /* * the stale event from a file descriptor * that was just closed in this iteration */ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "epoll: stale event %p", c); continue; } //是否延后 if (flags & NGX_POST_THREAD_EVENTS) { wev->posted_ready = 1; } else { wev->ready = 1; } if (flags & NGX_POST_EVENTS) { //加入到延迟操作的队列中 ngx_locked_post_event(wev, &ngx_posted_events); } else { //直接处理 wev->handler(wev); } } } ngx_mutex_unlock(ngx_posted_events_mutex); return NGX_OK;}
这里值得一提的是instance这个“用来判断事件是否过期的标志位”是如果工作的。
Nginx利用了指针的最后一位一定是0(指针一定是与操作系统的位数对齐的)这一特性。所以,在event_list[i].data.ptr获得ngx_connection_t指针的同时,将最后一位设置为这个事件instance标志。在去除来时,进行修改即可。
那么事件为什么会过期呢?比如:有3个事件等待处理,1事件中处理时,需要把3事件给关闭,如果只是把3事件的fd套接字设置为-1(原先为50),并调用ngx_free_connection是否有用的?答案是,依然可能失效。因为可能在处理2事件的时候调用ngx_get_connection又分配到了fd=50的套接字。那么,在处理3事件时,这个事件就不是过去的那个事件了,是过期的!但是3事件依旧以为“自己还是过去那个自己”。
于是,面对这种情况,Nginx在ngx_get_connection函数中,将从连接池中获取的新连接instance标志位取反。那么,当下一次使用这个连接时就发现instance发生了变化,认定为过期事件。
ngx_epoll_module epoll模块实现
ngx_module_t ngx_epoll_module = { NGX_MODULE_V1, &ngx_epoll_module_ctx, /* module context */ ngx_epoll_commands, /* module directives */ NGX_EVENT_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING};
- 《深入理解Nginx 模块开发与架构解析》笔记之epoll事件模块
- 《深入理解Nginx:模块开发与架构解析》笔记
- 《深入理解Nginx 模块开发与架构解析》笔记之定时器
- 深入理解Nginx:模块开发与架构解析
- 《深入理解Nginx模块开发与架构解析》勘误
- 深入理解Nginx 模块开发与架构解析-陶辉 读书笔记
- 《深入理解Nginx-模块开发与架构解析》
- 深入理解Nginx:模块开发与架构解析 读书笔记
- 推荐我的新书《深入理解Nginx:模块开发与架构解析》
- 【读书笔记】深入理解Nginx模块开发与架构解析(一)
- 【读书笔记】深入理解Nginx模块开发与架构解析(二)
- Nginx的命令行控制-转自《深入理解Nginx模块开发与架构解析》第2版
- 【Nginx】epoll事件驱动模块
- 《深入理解Nginx》之HTTP模块
- nginx事件模块解析
- 《深入理解Nginx》阅读与实践(一):Nginx过滤模块开发与实例
- Nginx 的 epoll 事件驱动模块
- Nginx基础. epoll事件驱动模块
- pthread_create 函数引发的几点思考
- 泛型依赖注入
- FlowLayout详解(二)——FlowLayout实现
- AutoRunner note
- 程序员,为什么千万不要重写代码?
- 《深入理解Nginx 模块开发与架构解析》笔记之epoll事件模块
- Tomcat 定制JNDI URL Resource
- Ubuntu上安装 Emacs 24的几种方法
- roman to integer
- 系统运维相关的知识
- vim乱码问题
- u3d菜鸟入门:游戏中的图集
- 数据结构与算法-线性表顺序存储结构插入操作的实现
- 单链表删除重复节点