nginx出于性能考虑采用类似lib_event的方式,自己对时间进行了cache,用来减少对gettimeofday()的调用,因为一般来说服务器对时间的精度要求不是特别的高,不过如果需要比较精确的timer,nginx还提供了一个timer_resolution指令用来设置时间精度,具体的机制再后面会做介绍。在ngx_times.c中提供了ngx_time_update()函数来更新时间缓存,另外还有一个在信号处理中用来更新cached_err_log_time的ngx_time_sigsafe_update()函数,其他地方都是从时间缓存中取得时间。

首先

#define ngx_process_events   ngx_event_actions.process_events

而ngx_event_actions为nginx的I/O模型接口函数结构体,封装如epoll, kqueue,select,poll等这些提供的接口,这里仅对epoll进行分析,其他类似,于是ngx_event_actions.process_events 对应ngx_epoll_module.c文件中的 ngx_epoll_process_events()函数,在这个函数中执行epoll_wait()返回后会调用ngx_time_update()更新时间缓存,也就是当epoll通知有事件到达或者epoll超时返回后会更新一次时间;最后在cache_manager的进程也调用ngx_time_update()维护自己的时间缓存,这里不做介绍。

第二种方式

 

ngx_timer_signal_handler(int signo)
 {
     ngx_event_timer_alarm = 1;

 #if 1
     ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer signal");
 #endif
 }

它非常简单,只是将ngx_event_timer_alarm设置为1,用来记录有SIGALRM信号发生了,这时在来看ngx_epoll_process_events()函数,epoll_wait()的timeout被设置为-1,如果epoll_wait()是被SIGALRM信号唤醒,则调用ngx_time_update()更新时间缓存,否则继续使用之前的时间缓存,因为setitimer()每隔ngx_timer_resolution毫秒总会产生一次SIGALRM信号,这样就保证了时间缓存的精度为ngx_timer_resolution毫秒。这里只介绍了worker进程的情况,其他进程类似。

 

ngx_time_update()和ngx_time_sigsafe_update()这两个函数的实现比较简单,但是还是有几个值得注意的地方,首先由于时间可能在信号处理中被更新,另外多线程的时候也可能同时更新时间(nginx现在虽然没有开放多线程,但是代码中有考虑),nginx使用了原子变量ngx_time_lock来对时间变量进行写加锁,而且nginx考虑到读时间的操作比较多,出于性能的原因没有对读进行加锁,而是采用维护多个时间slot的方式来尽量减少读访问冲突,基本原理就是,当读操作和写操作同时发生时(1,多线程时可能发生;2,当进程正在读时间缓存时,被一信号中断去执行信号处理函数,信号处理函数中会更新时间缓存),也就是读操作正在进行时(比如刚拷贝完ngx_cached_time->sec,或者拷贝ngx_cached_http_time.data进行到一半时),如果写操作改变了读操作的时间,读操作最终得到的时间就变混乱了。nginx这里采用了64个slot时间,也就是每次更新时间的时候都是更新下一个slot,如果读操作同时进行,读到的还是之前的slot,并没有被改变,当然这里只能是尽量减少了时间混乱的几率,因为slot的个数不是无限的,slot是循环的,写操作总有几率会写到读操作的slot上。不过nginx现在实际上并没有采用多线程的方式,而且在信号处理中只是更新cached_err_log_time,所以对其他时间变量的读访问是不会发生混乱的。 另一个地方是两个函数中都调用了 ngx_memory_barrier() ,实际上这个也是一个宏,它的具体定义和编译器及体系结构有关,gcc和x86环境下,定义如下:

#define ngx_memory_barrier()    __asm__ volatile ("" ::: "memory")

它的作用实际上还是和防止读操作混乱有关,它告诉编译器不要将其后面的语句进行优化,不要打乱其执行顺序,具体还是来看一下 ngx_time_update函数:

ngx_time_update()
{
  ...
 
   if (!ngx_trylock(&ngx_time_lock)) {
         return;
     }

    ...

 tp = &cached_time[slot]; 

    tp->sec = sec;
     tp->msec = msec; 

     ngx_gmtime(sec, &gmt);


    p0 = &cached_http_time[slot][0]; 

     (void) ngx_sprintf (p0, "%s, %02d %s %4d %02d:%02d:%02d GMT",
                        week[gmt.ngx_tm_wday], gmt.ngx_tm_mday,
                        months[gmt.ngx_tm_mon - 1], gmt.ngx_tm_year,
                        gmt.ngx_tm_hour, gmt.ngx_tm_min, gmt.ngx_tm_sec);

 #if (NGX_HAVE_GETTIMEZONE)

    tp->gmtoff = ngx_gettimezone(); 
     ngx_gmtime(sec + tp->gmtoff * 60, &tm);

 #elif (NGX_HAVE_GMTOFF)

     ngx_localtime(sec, &tm);
     cached_gmtoff = (ngx_int_t) (tm.ngx_tm_gmtoff / 60);
    tp->gmtoff = cached_gmtoff; 

 #else

     ngx_localtime(sec, &tm);
     cached_gmtoff = ngx_timezone(tm.ngx_tm_isdst);
    tp->gmtoff = cached_gmtoff; 

 #endif


    p1 = &cached_err_log_time[slot][0]; 

     (void) ngx_sprintf (p1, "%4d/%02d/%02d %02d:%02d:%02d",
                        tm.ngx_tm_year, tm.ngx_tm_mon,
                        tm.ngx_tm_mday, tm.ngx_tm_hour,
                        tm.ngx_tm_min, tm.ngx_tm_sec);


    p2 = &cached_http_log_time[slot][0]; 

     (void) ngx_sprintf (p2, "%02d/%s/%d:%02d:%02d:%02d %c%02d%02d",
                        tm.ngx_tm_mday, months[tm.ngx_tm_mon - 1],
                        tm.ngx_tm_year, tm.ngx_tm_hour,
                        tm.ngx_tm_min, tm.ngx_tm_sec,
                        tp->gmtoff < 0 ? '-' : '+',
                        ngx_abs(tp->gmtoff / 60), ngx_abs(tp->gmtoff % 60));


    ngx_memory_barrier(); 

    ngx_cached_time = tp;
     ngx_cached_http_time.data = p0;
     ngx_cached_err_log_time.data = p1;
     ngx_cached_http_log_time.data = p2; 

     ngx_unlock(&ngx_time_lock);}

可以看到ngx_memory_barrier()之后是四条赋值语句,如果没有 ngx_memory_barrier(),编译器可能会将 ngx_cached_time = tp ,ngx_cached_http_time.data = p0,ngx_cached_err_log_time.data = p1, ngx_cached_http_log_time.data = p2 分别和之前的 tp = &cached_time[slot] , p0 = &cached_http_time[slot][0] , p1 = &cached_err_log_time[slot][0] , p2 = &cached_http_log_time[slot][0] ngx_memory_barrier() 后,时间缓存更新到一致的