Nginx 源码阅读笔记3 时间管理

在 nginx 中,每个进程各自管理着自己的时间,而对于时间的管理则采用了缓存的方式,由于读取时间比更新时间频繁得多,而时间可能被信号处理函数或不同的线程(如果支持的话)更新,所以需要加锁,此时如果采用同一个变量来表示时间,则读取时间时也需要加锁,为了让读取操作免去加锁,nginx 使用了一个循环数组来缓存时间

变量声明

省略了一些相似的变量,比如描述 http 时间的字符串有好几种格式,所以有好几个数组

typedef struct {
    time_t      sec;
    ngx_uint_t  msec;
    ngx_int_t   gmtoff;
} ngx_time_t;

static ngx_uint_t        slot;
static ngx_atomic_t      ngx_time_lock;

#if !(NGX_WIN32)
static ngx_int_t         cached_gmtoff;
#endif

volatile ngx_msec_t      ngx_current_msec;
volatile ngx_time_t     *ngx_cached_time;
volatile ngx_str_t       ngx_cached_err_log_time;
volatile ngx_str_t       ngx_cached_http_time;

static ngx_time_t        cached_time[NGX_TIME_SLOTS];
static u_char            cached_err_log_time[NGX_TIME_SLOTS][sizeof("1970/09/28 12:00:00")];
static u_char            cached_http_time[NGX_TIME_SLOTS][sizeof("Mon, 28 Sep 1970 06:00:00 GMT")];

// 用于获取时间的函数
#define ngx_time()           ngx_cached_time->sec
#define ngx_timeofday()      (ngx_time_t *) ngx_cached_time

可以看到,用于获取时间的函数访问的是ngx_cached_time,而cached_time数组缓存了每次更新的值,slot则表示最后一次更新时的下标,cached_gmtoff表示与 GMT 相差的分钟数

更新时间

简单地说,更新时间就修改cached_time中下一个slot的值,并将ngx_cached_time指向这个位置,当然其他描述时间的字符串也需要更新

void
ngx_time_update(void)
{
    u_char          *p0, *p1, *p2, *p3, *p4;
    ngx_tm_t         tm, gmt;
    time_t           sec;
    ngx_uint_t       msec;
    ngx_time_t      *tp;
    struct timeval   tv;

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

    ngx_gettimeofday(&tv);

    sec = tv.tv_sec;
    msec = tv.tv_usec / 1000;

    ngx_current_msec = (ngx_msec_t) sec * 1000 + msec;

    tp = &cached_time[slot];

    if (tp->sec == sec) {   // sec 相同
        tp->msec = msec;
        ngx_unlock(&ngx_time_lock);
        return;
    }

    if (slot == NGX_TIME_SLOTS - 1) {
        slot = 0;
    } else {
        slot++;
    }

    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%02i%02i",
                       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));

    p3 = &cached_http_log_iso8601[slot][0];

    (void) ngx_sprintf(p3, "%4d-%02d-%02dT%02d:%02d:%02d%c%02i:%02i",
                       tm.ngx_tm_year, tm.ngx_tm_mon,
                       tm.ngx_tm_mday, 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));

    p4 = &cached_syslog_time[slot][0];

    (void) ngx_sprintf(p4, "%s %2d %02d:%02d:%02d",
                       months[tm.ngx_tm_mon - 1], tm.ngx_tm_mday,
                       tm.ngx_tm_hour, tm.ngx_tm_min, tm.ngx_tm_sec);

    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_cached_http_log_iso8601.data = p3;
    ngx_cached_syslog_time.data = p4;

    ngx_unlock(&ngx_time_lock);
}

首先判断秒数是否相同,相同的话只需简单更新毫秒的值即可,否则需要将值更新到下一个位置,因为秒数的改变可能引起年月日的变化,也就是需要修改字符串内的时间值,而这些字符串不关心毫秒的值
接下来是关于时区的判断,我的系统中没有定义NGX_HAVE_GETTIMEZONE这个特性测试宏,所以直接看NGX_HAVE_GMTOFF,这里调用了ngx_localtime函数,然后更新了cached_gmtoff的值,ngx_localtime的定义如下

void
ngx_localtime(time_t s, ngx_tm_t *tm)
{
#if (NGX_HAVE_LOCALTIME_R)
    (void) localtime_r(&s, tm);

#else
    ngx_tm_t  *t;

    t = localtime(&s);
    *tm = *t;

#endif

    tm->ngx_tm_mon++;
    tm->ngx_tm_year += 1900;
}

结果是在 tm 中存储本地时间,也就是带时区偏移的,这里最后还处理了月份和年份的值,可以看下 man 中的描述

struct tm {
    int tm_sec;    /* Seconds (0-60) */
    int tm_min;    /* Minutes (0-59) */
    int tm_hour;   /* Hours (0-23) */
    int tm_mday;   /* Day of the month (1-31) */
    int tm_mon;    /* Month (0-11) */
    int tm_year;   /* Year - 1900 */
    int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
    int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
    int tm_isdst;  /* Daylight saving time */
};

因为系统中月份的范围是 0 到 11,所以需要递增一次,但是年份为什么需要加上 1900 呢?查了一下资料发现,以前人们为了节省内存而使用两位数表示年份,比如用 60 表示 1960 年,当然,到了 2000 年则引发了著名的千年虫问题
时间更新函数最后更新了几个cached_time的值,需要注意的是这里调用了之前说过的内存屏障,想象一下,如果编译器为了优化性能而修改了执行顺序,比如改成如下形式

ngx_cached_http_time.data = p0;

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);

ngx_cached_err_log_time.data = p1;

由于ngx_cached_http_timecached_err_log_time的赋值语句中间间隔较多语句,可能引发的问题是,两者时间被读取时产生不一致,而将赋值操作全部留到最后则减少了不一致的可能性

信号安全的时间更新函数

void
ngx_time_sigsafe_update(void)
{
    u_char          *p, *p2;
    ngx_tm_t         tm;
    time_t           sec;
    ngx_time_t      *tp;
    struct timeval   tv;

    if (!ngx_trylock(&ngx_time_lock)) {  // 如果信号引发在更新时间时,则这里获取不到锁
        return;
    }

    ngx_gettimeofday(&tv);

    sec = tv.tv_sec;

    tp = &cached_time[slot];

    if (tp->sec == sec) {
        ngx_unlock(&ngx_time_lock);
        return;
    }

    if (slot == NGX_TIME_SLOTS - 1) {
        slot = 0;
    } else {
        slot++;
    }

    tp = &cached_time[slot];

    tp->sec = 0;

    ngx_gmtime(sec + cached_gmtoff * 60, &tm);

    p = &cached_err_log_time[slot][0];

    (void) ngx_sprintf(p, "%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_syslog_time[slot][0];

    (void) ngx_sprintf(p2, "%s %2d %02d:%02d:%02d",
                       months[tm.ngx_tm_mon - 1], tm.ngx_tm_mday,
                       tm.ngx_tm_hour, tm.ngx_tm_min, tm.ngx_tm_sec);

    ngx_memory_barrier();

    ngx_cached_err_log_time.data = p;
    ngx_cached_syslog_time.data = p2;

    ngx_unlock(&ngx_time_lock);
}

大体上和之前那个函数差不多,有两个需要注意的点,一是为了保证下次更新时间时会使用下一个slot 而将tp->sec的值设置为了 0,二是可这里只更新了两个log相关的值,原因应该是这个函数只在信号处理函数ngx_signal_handler里调用,而信号处理函数内涉及较多log操作
那么,为什么这个函数称为信号安全呢?关键点在于获取年月日时,这里没有调用ngx_localtime,因为库函数localtime和他的可重入版本localtime_r都不是异步信号安全的,那么要怎么才能获取到时区信息呢?这就需要用到之前更新的cached_gmtoff变量,里面缓存了与 GMT 相差的分钟数,据此可以算出本地时间

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值