文档章节

nginx源码分析——定时器

hncscwc
 hncscwc
发布于 2016/10/31 16:29
字数 1365
阅读 278
收藏 0

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返回值的说明

© 著作权归作者所有

hncscwc
粉丝 66
博文 70
码字总数 76137
作品 0
杭州
程序员
私信 提问
nginx源码分析—启动流程

作者:阿波 本文链接: http://blog.csdn.net/livelylittlefish/article/details/7243718 Content 0. 序 1. main()分析 2. 注意问题 2.1 几个初值 2.2 nginx工作模式 2.3 一些配置 2.4 其他开...

晨曦之光
2012/03/09
1K
0
nginx源码分析——http处理流程

http处理流程概述 nginx对于http请求处理的大概流程是:接收客户端发起的连接,然后接收并解析http请求行,接收并解析http头部;再根据配置文件nginx.conf找到相关http模块,使这些模块依次合...

hncscwc
2016/07/15
588
2
nginx源码分析—hash结构ngx_hash_t(v1.0.4)

本博客(http://blog.csdn.net/livelylittlefish )贴出作者(阿波)相关研究、学习内容所做的笔记,欢迎广大朋友指正! Content 0.序 1.hash结构 1.1ngx_hash_t结构 1.2ngx_hash_init_t结构...

晨曦之光
2012/03/09
763
0
libevent源码深度剖析

作者:http://blog.csdn.net/sparkliang/article/category/660506 libevent源码深度剖析十三——libevent信号处理注意点 libevent源码深度剖析十三——libevent信号处理注意点前面讲到了lib...

晨曦之光
2012/03/09
2.8K
0
nginx源码分析—队列结构ngx_queue_t

本博客(http://blog.csdn.net/livelylittlefish )贴出作者(阿波)相关研究、学习内容所做的笔记,欢迎广大朋友指正! Content 0. 序 1. 队列结构 2. 队列操作 2.1 在头节点之后插入 2.2 ...

晨曦之光
2012/03/09
242
2

没有更多内容

加载失败,请刷新页面

加载更多

Mybatis Plus删除

/** @author beth @data 2019-10-17 00:30 */ @RunWith(SpringRunner.class) @SpringBootTest public class DeleteTest { @Autowired private UserInfoMapper userInfoMapper; /** 根据id删除......

一个yuanbeth
今天
4
0
总结

一、设计模式 简单工厂:一个简单而且比较杂的工厂,可以创建任何对象给你 复杂工厂:先创建一种基础类型的工厂接口,然后各自集成实现这个接口,但是每个工厂都是这个基础类的扩展分类,spr...

BobwithB
今天
5
0
java内存模型

前言 Java作为一种面向对象的,跨平台语言,其对象、内存等一直是比较难的知识点。而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚。比如本文我们要讨论的JVM内存结构、Java内存模...

ls_cherish
今天
4
0
友元函数强制转换

友元函数强制转换 p522

天王盖地虎626
昨天
5
0
js中实现页面跳转(返回前一页、后一页)

本文转载于:专业的前端网站➸js中实现页面跳转(返回前一页、后一页) 一:JS 重载页面,本地刷新,返回上一页 复制代码代码如下: <a href="javascript:history.go(-1)">返回上一页</a> <a h...

前端老手
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部