欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 高考 > ngx_time_update

ngx_time_update

2025/11/10 22:29:23 来源:https://blog.csdn.net/weixin_41812346/article/details/146852179  浏览:    关键词:ngx_time_update

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_time_update函数-CSDN博客


定义在 src\core\ngx_times.c

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_monotonic_time(sec, msec);tp = &cached_time[slot];if (tp->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;#elsengx_localtime(sec, &tm);cached_gmtoff = ngx_timezone(tm.ngx_tm_isdst);tp->gmtoff = cached_gmtoff;#endifp1 = &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);
}

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

尝试获取一个互斥锁,失败时直接返回

ngx_trylock 是一个非阻塞的锁尝试函数

如果 ngx_time_lock 已被其他线程持有,ngx_trylock 会立即返回失败,而不是阻塞等待

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_trylock函数-CSDN博客


    ngx_gettimeofday(&tv);sec = tv.tv_sec;msec = tv.tv_usec / 1000;

ngx_gettimeofday 函数

定义在 src/os/unix/ngx_time.h 中:

#define ngx_gettimeofday(tp)  (void) gettimeofday(tp, NULL);

struct timeval *tv

是一个指向 timeval 结构体的指针,用于存储当前时间

struct timezone *tz

是一个指向 timezone 结构体的指针,用于存储时区信息

timezone 结构体定义如下

struct timezone {int tz_minuteswest; // 西侧相对于 UTC 的分钟差int tz_dsttime;     // 夏令时修正类型(已废弃,通常设为 NULL)
};

 

在现代程序中,tz 参数通常设置为 NULL,因为时区信息可以通过其他更可靠的方式获取(例如通过环境变量或系统调用)

返回值

  • 如果调用成功,gettimeofday 返回 0
  • 如果调用失败,返回 -1,并设置 errno 来指示错误原因

获取秒(tv_sec)和微秒(tv_usec

sec = tv.tv_sec;

从 struct timeval 结构体中提取出秒数部分,赋值给变量 sec

msec = tv.tv_usec / 1000;

从 struct timeval 结构体中提取出微秒部分,并将其转换为毫秒,赋值给变量 msec

此时

sec=1743410554
msec=267


ngx_current_msec = ngx_monotonic_time(sec, msec);

ngx_current_msec 是 Nginx 内部维护的一个全局变量,用于存储当前的时间值(以毫秒为单位)

ngx_monotonic_time 是 Nginx 提供的一个函数,用于将时间值(秒和毫秒)转换为单调递增的时间值(monotonic time)

这行代码的意图是将当前的时间值(sec 和 msec)转换为单调时间值,并更新到 ngx_current_msec 中

单调时间值通常表示自某个固定起点(例如系统启动时间)以来经过的时间,而不是绝对的日历时间

不会因为系统时钟调整(如 NTP 时间同步、人为更改系统时间等)而回退或突然变小

ngx_monotonic_time-CSDN博客

 


tp = &cached_time[slot];

获取对时间缓存数组 cached_time 中特定位置的引用,并将其赋值给指针 tp

此时 

slot=0


 

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

检查缓存的时间(tp->sec)是否与当前获取的时间(sec)一致,是否仍处于同一秒内

此时

tp->sec=0 (还未设置)
sec=1743425782

条件不成立


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

检查当前 slot 是否已经到达缓存数组 cached_time 的最后一个位置

NGX_TIME_SLOTS 是数组的大小,因此最后一个索引是 NGX_TIME_SLOTS - 1 

如果当前 slot 已经到达数组末尾,则将 slot 重置为 0,表示循环到数组的起始位置。

如果当前 slot 尚未到达数组末尾,则将 slot 增加 1,继续遍历数组。

此时 

slot=0
NGX_TIME_SLOTS=64

进入 else

执行后

slot=1


    tp = &cached_time[slot];tp->sec = sec;tp->msec = msec;

获取下一个元素,更新它的时间


    ngx_gmtime(sec, &gmt);

将秒数时间值(sec)转换为人类可读的 GMT 时间格式 

sec(一个 time_t 类型的时间值,表示自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数)转换为 GMT 时间,并将结果存储在 gmt 结构体中。

ngx_gmtime 用于将时间值转换为 GMT 时间。


 

ngx_gmtime-CSDN博客


p0 = &cached_http_time[slot][0];

获取当前时间槽(slot)对应的 HTTP 时间缓存字符串的起始地址

cached_http_time 是一个二维数组,每个 slot 对应一个预分配的字符数组,用于存储格式化后的 HTTP 时间字符串。

slot 是一个循环索引,通过轮换槽位实现无锁更新:写入新时间到下一个槽位后,原子切换全局指针,避免读写竞争。

为格式化的时间字符串(如 HTTP 报文头中的时间信息)准备一个存储空间。

通过直接操作缓冲区地址,避免频繁的内存分配和释放,提高效率

此时 

slot=1

p0 所指向的还是空串


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

ngx_sprintf 函数将格式化的时间字符串存储到 p0 指向的缓冲区中

格式:"%s, %02d %s %4d %02d:%02d:%02d GMT"

分解:

%s:星期缩写(如"Mon")
%02d:两位数的日期(不足补零)
%s:月份缩写(如"Jan")
%4d:四位数的年份
%02d:%02d:%02d:时:分:秒(两位数格式)
GMT:固定时区标识


week[gmt.ngx_tm_wday]

获取星期几的缩写

gmt.ngx_tm_wday:0-6(0=Sunday)

week[]:预定义的星期字符串数组

src\core\ngx_times.c 中:

static char  *week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

months[gmt.ngx_tm_mon - 1] 
获取月份缩写

gmt.ngx_tm_mon:1-12

months[]:预定义的月份字符串数组(如["Jan", "Feb", ...])

-1:调整数组索引(从0开始)

src\core\ngx_times.c 中:

static char  *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

ngx_sprintf-CSDN博客

 p0=Wed, 02 Apr 2025 15:23:19 GMT


#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;#elsengx_localtime(sec, &tm);cached_gmtoff = ngx_timezone(tm.ngx_tm_isdst);tp->gmtoff = cached_gmtoff;#endif

NGX_HAVE_GETTIMEZONE 未定义 

NGX_HAVE_GMTOFF=1

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

这部分代码成立


 

ngx_localtime(sec, &tm);

将秒级时间戳转换为本地时间结构体

输入 sec 是自 Epoch 的秒数(UTC 时间)

输出 tm 是包含本地时间的结构体(含时区信息)

ngx_localtime

ngx_localtime-CSDN博客


 cached_gmtoff = (ngx_int_t)(tm.ngx_tm_gmtoff / 60);

计算并缓存时区偏移(以分钟为单位)

ngx_tm_gmtoff 成员是以秒为单位的时区偏移

除以 60 转换为分钟

强制转换为 ngx_int_t


tp->gmtoff = cached_gmtoff;

将时区偏移值存储到时间结构体

tp 指向当前时间槽的缓存时间结构体

gmtoff 字段用于存储时区偏移


代码段整体逻辑:

通过 ngx_localtime 获取本地时间(含时区信息)
从 tm 结构体提取秒级时区偏移,转换为分钟单位
将计算结果缓存到全局变量和当前时间结构体


p1 = &cached_err_log_time[slot][0];

将指针 p1 指向二维数组 cached_err_log_time 中当前时间槽(slot)对应的字符串缓存的首地址

cached_err_log_time 是一个预分配的二维数组,用于存储不同时间槽的错误日志时间字符串。

slot 是当前时间槽的索引

[slot][0] 表示取第 slot 个时间槽的字符串首地址

cached_err_log_time 是一个缓存变量,用于存储格式化后的错误日志时间字符串。

它的主要作用是优化错误日志记录的性能 ,避免每次记录错误日志时都重新格式化时间字符串。

确保不同线程或请求之间不会相互干扰


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

将时间数据格式化为字符串并写入 p1 指向的内存

格式字符串 "%4d/%02d/%02d %02d:%02d:%02d":

%4d:4 位 年份(如 2023)

/%02d:2 位 月份(不足补零,如 03 表示三月)

/%02d:2 位 日期(不足补零,如 05)

%02d:%02d:%02d:2 位 小时、分钟、秒(不足补零)

时间字段:

tm.ngx_tm_year:四位数的年份(如 2023)。

tm.ngx_tm_mon:月份

tm.ngx_tm_mday:日期

tm.ngx_tm_hour:小时

tm.ngx_tm_min:分钟

tm.ngx_tm_sec:秒

输出示例:2023/10/05 15:30:45

意图:

生成一个易读的本地时间字符串,用于错误日志的时间戳。

缓存此字符串,避免每次记录日志时重复格式化时间,提升性能。


p1=2025/04/03 13:12:55


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

获取当前时间槽(slot)对应的缓存位置

cached_http_log_time 是一个二维数组,每个槽(slot)存储一个时间字符

p2 指向当前槽的首地址,准备写入新的时间字符串


将格式化后的时间字符串写入 p2 指向的缓存

%02d:两位数的日期(不足补零,如 02)。
/%s/:缩写的月份(如 Oct)。
%d:四位数的年份(如 2023)。
%02d:%02d:%02d:两位数的时、分、秒(如 14:05:09)。
%c%02i%02i:时区符号(+或-)和两位数的时区偏移(如 +0800)
tm.ngx_tm_mday:当前日期(1-31)。
months[tm.ngx_tm_mon - 1]:月份缩写。
months 是预定义的月份缩写数组(如 Jan, Feb 等)。



时间部分

tm.ngx_tm_year:四位数的年份(如 2023)。
tm.ngx_tm_hour:小时(0-23)。
tm.ngx_tm_min:分钟(0-59)。
tm.ngx_tm_sec:秒(0-59)。
时区部分

tp->gmtoff < 0 ? '-' : '+':时区符号(负偏移用 -,正偏移用 +)。
ngx_abs(tp->gmtoff / 60):时区偏移的小时部分(绝对值)。
ngx_abs(tp->gmtoff % 60):时区偏移的分钟部分(绝对值)。
tp->gmtoff 以分钟为单位的时区偏移


看一下对

%c%02i%02i

的处理

ngx_vslprintf

ngx_vslprintf-CSDN博客


p2=03/Apr/2025:14:53:47 +0800 


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

p3 = &cached_http_log_iso8601[slot][0]; 

cached_http_log_iso8601 是一个二维数组,每个槽位(slot)存储一个预生成的 ISO 8601 时间字符串。

slot 表示当前活跃的缓存槽位,通过轮换槽位避免多线程/异步环境下的读写冲突。

p3 指向当前槽位的起始地址,后续代码将时间字符串写入此处


(void) ngx_sprintf(p3, "%4d-%02d-%02dT%02d:%02d:%02d%c%02i:%02i", ...);

作用:将当前时间和时区信息格式化为 ISO 8601 字符串,并写入 p3 指向的缓存。

参数解析:

格式字符串:"%4d-%02d-%02dT%02d:%02d:%02d%c%02i:%02i"

%4d:4 位 年份(如 2023)。

%02d:2 位 月份(如 01 表示 1 月)。

%02d:2 位 日期(如 05)。

T:字面量字符,分隔日期和时间。

%02d:2 位 小时(00-23)。

%02d:2 位 分钟(00-59)。

%02d:2 位 秒(00-59)。

%c:时区符号(+ 或 -)。

%02i:%02i:时区偏移的小时和分钟部分(如 +08:00)。

实际参数:

tm.ngx_tm_year:年份(如 2023)。

tm.ngx_tm_mon:月份(1-12,直接使用无需调整)。

tm.ngx_tm_mday:日期(1-31)。

tm.ngx_tm_hour:小时(0-23)。

tm.ngx_tm_min:分钟(0-59)。

tm.ngx_tm_sec:秒(0-59)。

tp->gmtoff < 0 ? '-' : '+':时区符号(负值为 -,正值为 +)。

ngx_abs(tp->gmtoff / 60):时区偏移的小时部分(绝对值)。

ngx_abs(tp->gmtoff % 60):时区偏移的分钟部分(绝对值)。


p3=2025-04-03T14:53:47+08:00


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

p4=Apr  3 14:53:47


ngx_memory_barrier();

ngx_memory_barrier() 是内存屏障(Memory Barrier),用于解决多线程环境下的 内存可见性 和 指令重排序 问题:

内存可见性:确保当前线程对内存的修改对其他线程立即可见。
指令重排序:防止编译器或 CPU 将屏障前后的指令乱序执行。
在 Nginx 这种高并发场景中,时间更新可能被多个线程(如 Worker 进程)竞争访问,内存屏障能确保全局时间变量的更新是原子且一致的。

ngx_memory_barrier-CSDN博客


    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_unlock 函数

src/os/unix/ngx_atomic.h

#define ngx_unlock(lock)    *(lock) = 0

将 lock 指向的内存值赋 0,释放之前获取的互斥锁


版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词