nginx源码分析——定时器
2016-10-31 16:29
375 查看
1. 概述
nginx实现了自己的定时器触发机制,它与epoll等事件驱动模块处理的网络事件不同;在网络事件中,网络事件的触发是由内核完成的,而定时器事件则完全是由nginx自身实现的,它与内核完全无关。定时器的使用及处理流程分两步:
第一步:初始化事件,设置事件的回调处理函数,然后通过ngx_add_timer()添加定时器事件。
第二步:事件循环,等待超时回调。
对于使用者而言,只需要设置事件的回调函数,添加定时器事件,等待回调进行处理即可。
2. 相关接口与变量
初始化定时器ngx_int_t ngx_event_timer_init( ngx_log_t * log ) { ngx_rbtree_init( &ngx_event_timer_rbtree, &ngx_event_timer_sentinel, ngx_rbtree_insert_timer_value); return NGX_OK; }
该接口主要是初始化用于存储定时器的全局变量ngx_event_timer_rbtree。
查找定时器
ngx_msec_t ngx_event_find_timer( void ) { ngx_msec_int_t timer; ngx_rbtree_node_t *node, *root, *sentinel; if( ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel ) { return NGX_TIMER_INFINITE; } root = ngx_event_timer_rbtree.root; sentinel = ngx_event_timer_rbtree.sentinel; node = ngx_rbtree_min( root, sentinel ); timer = (ngx_msec_int_t)(node->key - ngx_current_msec); return (ngx_msec_t)(timer > 0 ? timer : 0); }
实质上是获取最近一个即将超时的事件距离当前时间还有多少毫秒,如果已经超时则返回0,如果没有定时器事件,则返回-1。
超时事件处理
void ngx_event_expire_timers(void) { ngx_event_t * ev; ngx_rbtree_node_t *node, *root, *sentinel; sentinel = ngx_event_timer_rbtree.sentinel; for( ; ; ) { root = ngx_event_timer_rbtree.root; if(root == sentinel) { return ; } node = ngx_rbtree_min(root, sentinel); if( (ngx_msec_int_t)(node->key - ngx_current_msec) > 0 ) { // 最早的一个定时器都未超时, 直接返回 return ; } // 取出定时器事件 ev = (ngx_event_t*)((char*)node - offsetof(ngx_event_t, timer)); // 删除定时器 ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer); ev->timer_set = 0; // 设置超时标识 ev->timedout = 1; // 回调处理 ev->handler(ev); } }
添加定时器
static ngx_inline void ngx_event_add_timer( ngx_event_t * ev, ngx_msec_t timer) { ngx_msec_t key; ngx_msec_int_t diff; // 计算超时的时间 key = ngx_current_msec + timer; if( ev->timer_set ) { // 如果该事件已经设置过定时器, 先后两次设置定时器必须大于默认的最小时间 diff = (ngx_msec_int_t)(key - ev->timer.key); if(ngx_abs(diff) < NGX_TIMER_LAZY_DELAY) { return; } // 大于默认的最小时间, 则删除老的, 以新的为准 ngx_del_timer(ev); } ev->timer.key = key; // 添加到定时器 红黑树中 ngx_rbtree_insert( &ngx_event_timer_rbtree, &ev->timer); // 置标识位 ev->timer_set = 1; }
删除定时器
static ngx_inline void ngx_event_del_timer( ngx_event_t * ev ) { ngx_rbtree_delete( &ngx_event_timer_rbtree, &ev->timer); ev->timer_set = 0; }
全局变量
// 用来保存定时器 ngx_rbtree_t ngx_event_timer_rbtree; // 系统当前时间 volatile ngx_msec_t ngx_current_msec;
----------------------------------------------------------------------------------------------------------------------------------------------
3. 全局变量当前时间的更新
从前面的源码可以看出,定时器会依赖于记录当前时间的全局变量ngx_current_msec,添加定时器是需要根据当前时间计算最终的超时时间;事件循环处理时,也需要根据该变量判断超时是否发生,那么这个变量什么时候会进行更新呢?1). 通常情况下,全局变量ngx_current_msec的更新会依赖于ngx_process_events传入的时间参数timer。对于使用epoll模块的情况,timer最终会作为epoll_wait函数调用的参数。
事件循环的处理过程中,会从定时器里找到最近一个即将超时的时间,同时设置标志需要进行时间的更新,epoll_wait函数返回后,调用ngx_time_update更新ngx_current_msec。当没有设置定时器时,epoll_wait等待的时间为-1,即阻塞等待直到有事件触发才返回。
2). 在子进程个数大于1个的情况下,可以通过配置项accept_mutex_delay控制epoll_wait等待的时长。
3). 通过设置 timer_resolution指定更新的时间粒度
当配置了timer_resolution以后,nginx在启动时,会通过setitimer告诉内核,每隔多长事件发送一次SIGALRM信号,同时设置捕获信号的回调处理函数,在这个函数中设置标志位告诉事件处理模块需要进行事件的更新。需要注意的是,即便是在没有设置定时器的情况下,虽然通过ngx_process_events传递给epoll_wait的时间仍旧为-1,但是由于有信号的中断,epoll_wait会返回EINTR,而不是阻塞等待直到有事件触发。这样就达到了按照指定的时间粒度,更新全局变量ngx_current_msec了。
// Ngx_event.c static void ngx_timer_signal_handler( int signo ) { // 置全局标志位为1 事件循环处理过程中判断该标识位决定是否要更新记录当前时间的全局变量 ngx_event_timer_alarm = 1; } static ngx_int_t ngx_event_process_init( ngx_cycle_t * cycle ) { if( ccf->master && ccf->worker_processses > 1 && ecf->accept_mutex ){ ngx_use_accept_mutex = 1; ngx_accept_mutex_held = 0; ngx_accept_mutex_delay = ecf->accept_mutex_delay; } else { ngx_use_accept_mutex = 0; } ... // 配置timer_resolution的情况,设置信号捕获函数, 然后调用setitimer定时更新 if( ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT) ){ struct sigaction sa; struct itimerval itv; ngx_memzero(&sa, sizeof(struct sigaction)); sa.sa_handler = ngx_timer_signal_handler; sigemptyset(&sa.sa_mask); if( sigaction(SIGALRM, &sa, NULL) == -1 ){ return NGX_ERROR; } itv.it_interval.tv_sec = ngx_timer_resolution / 1000; itv.it_interval.tv_usec = (ngx_timer_resolution%1000) * 1000; itv.it_value.tv_sec = ngx_timer_resolution / 1000; itv.it_value.tv_usec = (ngx_timer_resolution%1000) * 1000; if( setitimer(ITIMER_REAL, &itv, NULL) == -1 ){ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "setitimer() failed"); } } .... } void ngx_process_events_and_timers( ngx_cycle_t * cycle ) { if( ngx_timer_resolution ) { // 设置有 timer_resolution 的情况 timer = NGX_TIMER_INFINITE; flags = 0; } else { // 计算到最近一个即将超时的时间, 没有定时器 则timer等于-1 timer = ngx_event_find_timer(); // 需要更新时间的标志 flags = NGX_UPDATE_TIME; } 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 { // 根据accept_mutex_delay 控制最小更新时间间隔( 默认配置值为500ms ) if( timer == NGX_TIMER_INFINITE || timer > ngx_accept_mutex_delay ) { timer = ngx_accept_mutex_delay } } } } .... delta = ngx_current_msec; (void)ngx_process_events(cycle, timer, flags); delta = ngx_current_msec - delta; // 处理定时器事件 if( delta ) { ngx_event_expire_timers(); } .... } // Ngx_epoll_module.c static ngx_int_t ngx_epoll_process_events(ngx_cycle_t * cycle, ngx_msec_t timer, ngx_uint_t flags) { // timer作为epoll_wait的参数 events = epoll_wait(ep, event_list, (int)nevents, timer); // 根据标志位更新时间 或者 设置了timer_resolution指定需要更新时间 if(flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { ngx_time_update(); } .... }
----------------------------------------------------------------------------------------------------------------------------------------------
补充:setitimer的说明与epoll_wait返回值的说明
相关文章推荐
- Nginx源码分析-定时器的实现及使用
- nginx的定时器源码分析
- nginx源码分析(2)- 概览
- nginx源码分析(4)-方法(1)
- Nginx源码分析-数组
- Nginx源码分析-进程管理之master进程
- Nginx源码分析-启动初始化过程(二)
- Nginx源码分析-启动初始化过程(一)
- JOTM中定时器的源码分析
- Nginx源码分析-事件循环
- nginx源码分析(19)-方法(3)
- Nginx源码分析-进程管理之worker进程
- nginx源码分析(5)-方法(2)
- nginx源码分析(10)-启动过程分析
- nginx源码分析(1)- 缘起
- nginx源码分析(15)-模块分析(1)
- nginx源码分析(3)- 自动脚本
- nginx源码分析(8)-模块化(3)
- nginx源码分析(12)-进程启动分析(2)
- nginx源码分析(1):hash的使用