Ubuntu 下 nginx-1.24.0 源码分析 - ngx_time_update函数
定义在 src\core\ngx_times.c 中
ngx_time_init 函数后面
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); }
函数
ngx_time_update
是 Nginx 中用于高效更新时间缓存的核心函数,目的是通过预生成多种时间格式,避免频繁的系统调用和格式化开销,从而提升性能。
1. 加锁与时间获取
- 通过 `ngx_trylock` 尝试获取 `ngx_time_lock` 锁,若失败则直接返回(避免阻塞)。
- 调用 `ngx_gettimeofday` 获取当前时间(秒 `tv.tv_sec` 和微秒 `tv.tv_usec`),并计算毫秒 `msec`。
- 更新全局单调时间 `ngx_current_msec`(用于高性能计时,避免系统时间跳变的影响)。2. 时间缓存更新
- 使用 `cached_time` 数组作为循环缓冲区(`NGX_TIME_SLOTS` 个槽位),存储秒级时间戳和毫秒。
- 若当前槽位的秒数未变化(`tp->sec == sec`),仅更新毫秒并返回,减少重复计算。
- 若时间已跨秒,切换到下一个槽位(循环),更新秒和毫秒。3. 时间格式化
- GMT 时间:生成 HTTP 标准日期格式(如 `Wed, 21 Oct 2020 12:34:56 GMT`),存于 `cached_http_time`。
- 本地时间:根据时区配置(`gmtoff`)计算本地时间,生成多种日志格式:
- 错误日志格式(`2020/10/21 12:34:56`)。
- HTTP 访问日志格式(`21/Oct/2020:12:34:56 +0800`)。
- ISO8601 格式(`2020-10-21T12:34:56+08:00`)。
- Syslog 格式(`Oct 21 12:34:56`)。4. 内存屏障与全局发布
- 通过 `ngx_memory_barrier` 确保缓存数据写入完成。
- 更新全局指针(如 `ngx_cached_time`、`ngx_cached_http_time.data`),使新时间数据对其他线程可见。5. 释放锁
- 完成更新后释放 `ngx_time_lock`。意图
1. 高性能时间管理
- 通过缓存时间数据和预格式化字符串,避免每次获取时间时的重复计算(如 `ngx_sprintf`),显著减少系统调用和格式化开销。
- 使用循环缓冲区(多槽位)实现无锁读取:写入新槽位时,其他线程仍可读取旧槽位的有效数据。2. 线程安全与原子性
- 通过 `ngx_time_lock` 保证时间更新的原子性。
- 通过内存屏障确保全局指针更新的可见性,避免部分更新导致的数据不一致。3. 平台兼容性
- 通过条件编译处理不同平台的时区获取方式(`NGX_HAVE_GETTIMEZONE`、`NGX_HAVE_GMTOFF`),增强跨平台兼容性。4. 多样化时间格式支持
- 预生成 HTTP 头、日志、Syslog 等场景所需的时间格式,满足不同模块的需求,提升整体效率。关键设计思想
- 空间换时间:牺牲少量内存缓存多种时间格式,换取 CPU 和 I/O 效率。
- 无锁读优化:通过多槽位设计,允许读操作无需加锁,适合高并发场景。
- 模块化时间处理:将时间更新逻辑集中管理,降低模块耦合度。此函数是 Nginx 高性能的核心组件之一,通过精细的优化确保时间处理在极端负载下仍能保持高效。
u_char *p0, *p1, *p2, *p3, *p4;
用于指向不同时间格式字符串的指针
ngx_tm_t tm, gmt;
Ubuntu 下 nginx-1.24.0 源码分析 ngx_tm_t 类型-CSDN博客
ngx_tm_t
是一个表示时间结构的自定义类型。
ngx_tm_t tm, gmt;
的作用是声明两个变量tm
和gmt
,它们分别用于存储本地时间和格林威治标准时间(GMT)的日期和时间信息。
time_t sec;
存储当前时间的秒数
time_t
是 C 标准库中用于表示时间的数据类型,通常是一个整数,表示从 1970 年 1 月 1 日 00:00:00 UTC 开始到当前时间的秒数(即 Unix 时间戳)。
ngx_uint_t msec;
存储当前时间的毫秒部分
ngx_time_t *tp;
指向一个存储当前时间信息的结构体
用于存储和操作当前时间的信息
ngx_times.h 中:
typedef struct {time_t sec;ngx_uint_t msec;ngx_int_t gmtoff; } ngx_time_t;
秒数(
sec
)、毫秒数(msec
)以及时间偏移量(gmtoff
)
struct timeval tv;
用于存储当前时间的秒数和微秒数
struct timeval
是 C 标准库中定义的一种时间结构体,用于存储秒(tv_sec
)和微秒(tv_usec
)级别的时间信息
struct timeval
是一个在 POSIX 标准中定义的结构体,用于表示时间,通常以秒和微秒为单位。它在<sys/time.h>
头文件中定义。
struct timeval
的定义如下:struct timeval {long tv_sec; /* 秒 */long tv_usec; /* 微秒 */ };
成员变量
tv_sec
:表示从 1970 年 1 月 1 日 00:00:00 UTC(Unix 纪元)开始的秒数。
tv_usec
:表示秒后的微秒数(范围 0-999999)
if (!ngx_trylock(&ngx_time_lock)) {return;}
这段代码中的
if (!ngx_trylock(&ngx_time_lock)) { return; }
是用来尝试获取一个互斥锁(ngx_time_lock
),以确保
ngx_time_update
函数的临界区(即需要同步访问的代码部分)只会被一个线程执行。如果锁获取失败,函数会直接返回,不执行后续的逻辑
ngx_trylock
是一个非阻塞的锁尝试函数如果
ngx_time_lock
已被其他线程持有,ngx_trylock
会立即返回失败,而不是阻塞等待。成功获取锁时:返回
true
,函数继续执行法获取锁时:返回
false
,函数直接返回,不执行后续的逻辑
ngx_time_lock
互斥锁
在 src\core\ngx_times.c 中定义的全局变量
static ngx_atomic_t ngx_time_lock;
ngx_time_lock
是一个原子锁变量,用于协调多进程(Nginx 的 Worker 进程)对共享时间数据的访问,通过ngx_time_lock
确保原子性
ngx_atomic_t
src\os\unix\ngx_atomic.h 中:
typedef volatile ngx_atomic_uint_t ngx_atomic_t;
typedef long ngx_atomic_int_t; typedef unsigned long ngx_atomic_uint_t;
所以本质上 无符号 long 类型
ngx_trylock 函数
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_trylock函数-CSDN博客
ngx_gettimeofday(&tv);
ngx_gettimeofday
是 Nginx 中用于获取当前系统时间的一个函数。它返回当前时间的秒数和微秒数,存储在
struct timeval
结构体中。tv.tv_sec
表示秒数,tv.tv_usec
表示微秒数
ngx_gettimeofday 函数
src/os/unix/ngx_time.h 中:
#define ngx_gettimeofday(tp) (void) gettimeofday(tp, NULL);
gettimeofday
是 C 语言中的一个函数,用于获取当前的时间。它属于 POSIX 标准,通常在类 Unix 系统(如 Linux 和 macOS)中使用。该函数可以返回自 1970年1月1日 00:00:00 UTC (即 Unix 时间起点)以来的秒数和微秒数函数原型
#include <sys/time.h>int gettimeofday(struct timeval *tv, struct timezone *tz);
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
来指示错误原因
sec = tv.tv_sec; msec = tv.tv_usec / 1000;
这两行代码的作用是将获取到的系统时间(
struct timeval
)拆分为秒和毫秒部分
sec = tv.tv_sec;
从
struct timeval
结构体中提取出秒数部分,赋值给变量sec
msec = tv.tv_usec / 1000;
从
struct timeval
结构体中提取出微秒部分,并将其转换为毫秒,赋值给变量msec
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_current_msec全局变量
src\core\ngx_times.c 中:
volatile ngx_msec_t ngx_current_msec;
ngx_msec_t 类型本质上是 uintptr_t
ngx_monotonic_time函数
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_monotonic_time函数-CSDN博客
tp = &cached_time[slot];
获取对时间缓存数组
cached_time
中特定位置的引用,并将其赋值给指针tp
cached_time
是一个全局数组,用于存储时间值及其相关数据。它是一个缓存机制,用于减少频繁调用系统时间函数的开销
cached_time
使用环形缓冲区(Ring Buffer)设计,slot
表示当前活跃的缓存槽索引当时间跨越整秒时,切换到下一个槽(
slot++
),确保新时间写入新槽,避免读写冲突
slot
src\core\ngx_times.c 中
static ngx_uint_t slot;
cached_time
src\core\ngx_times.c 中
static ngx_time_t cached_time[NGX_TIME_SLOTS];
#define NGX_TIME_SLOTS 64
数组的作用是存储多个时间槽的数据,每个槽位保存一个时间点的信息
定期更新
cached_time
中的时间数据在多线程或多进程环境中,不同线程或进程可以使用不同的槽位,从而减少对共享资源的竞争
即使某个槽位正在被更新,其他槽位仍然可以被读取,保证了时间读取的高效性
if (tp->sec == sec) {tp->msec = msec;ngx_unlock(&ngx_time_lock);return;}
if (tp->sec == sec)
:检查缓存的时间(
tp->sec
)是否与当前获取的时间(sec
)一致,是否仍处于同一秒内若相等,说明时间未跨越整秒,只需更新毫秒部分
若不相等,说明已进入新的一秒,需触发完整的时间更新流程
避免在同一秒内重复生成时间字符串(如 HTTP 时间、日志时间),减少计算开销。
高频时间更新场景下(如每毫秒调用一次),此检查能显著提升性能。
tp->msec = msec;
如果时间值的秒数部分未发生变化,仅更新时间缓存中的毫秒数(
msec
)
ngx_unlock(&ngx_time_lock);
释放之前获取的互斥锁(
ngx_time_lock
),以允许其他线程访问时间更新函数如果时间值的秒数未发生变化,缓存更新已经完成,可以提前释放锁
防止因长时间持有锁导致其他线程等待,提升并发性能
ngx_unlock 函数
src/os/unix/ngx_atomic.h:310:#define ngx_unlock(lock) *(lock) = 0
src/os/unix/ngx_atomic.h 中:
#define ngx_unlock(lock) *(lock) = 0
将 lock 指向的内存值赋 0,释放之前获取的互斥锁
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
,继续遍历数组。通过轮换时间槽的索引,实现了时间缓存的循环复用
tp = &cached_time[slot];
将指针
tp
指向cached_time
数组中索引为slot
的位置
tp->sec = sec;tp->msec = msec;
将当前时间的秒数(
sec
)存储到时间缓存对象tp
中将当前时间的毫秒数(
msec
)存储到时间缓存对象tp
中
ngx_gmtime(sec, &gmt);
将秒数时间值(
sec
)转换为人类可读的 GMT 时间格式
sec
(一个time_t
类型的时间值,表示自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数)转换为 GMT 时间,并将结果存储在gmt
结构体中。
ngx_gmtime
用于将时间值转换为 GMT 时间。
ngx_gmtime 函数
src\core\ngx_times.h 中:
void ngx_gmtime(time_t t, ngx_tm_t *tp);
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_gmtime 函数-CSDN博客
p0 = &cached_http_time[slot][0];
获取当前时间槽(
slot
)对应的 HTTP 时间缓存字符串的起始地址
cached_http_time
是一个二维数组,每个slot
对应一个预分配的字符数组,用于存储格式化后的 HTTP 时间字符串。
slot
是一个循环索引,通过轮换槽位实现无锁更新:写入新时间到下一个槽位后,原子切换全局指针,避免读写竞争。为格式化的时间字符串(如 HTTP 报文头中的时间信息)准备一个存储空间。
通过直接操作缓冲区地址,避免频繁的内存分配和释放,提高效率
cached_http_time
src\core\ngx_times.c
static u_char cached_http_time[NGX_TIME_SLOTS][sizeof("Mon, 28 Sep 1970 06:00:00 GMT")];
cached_http_time
是一个二维数组,用于缓存多个 HTTP 时间格式的字符串每个槽位存储一个时间字符串,格式为
"Mon, 28 Sep 1970 06:00:00 GMT"
(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
指向的缓冲区中
(void) ngx_sprintf(...)
显式忽略返回值,
ngx_sprintf
是Nginx自带的字符串格式化函数,返回写入的字节数,但这里不需要使用这个返回值。
p0
指针指向预分配的缓存数组
cached_http_time[slot][0]
,用于存储格式化后的HTTP时间字符串格式字符串解析
格式:
"%s, %02d %s %4d %02d:%02d:%02d GMT"
分解:
%s
:星期缩写(如"Mon")%02d
:两位数的日期(不足补零)%s
:月份缩写(如"Jan")%4d
:四位数的年份%02d:%02d:%02d
:时:分:秒(两位数格式)GMT
:固定时区标识
ngx_sprintf
src\core\ngx_string.h 中:
u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...);
src\core\ngx_string.c 中:
u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...) {u_char *p;va_list args;va_start(args, fmt);p = ngx_vslprintf(buf, (void *) -1, fmt, args);va_end(args);return p; }
ngx_sprintf
是一个用于格式化字符串的函数,类似于 C 标准库中的sprintf
底层实现依赖于
ngx_vslprintf
week[gmt.ngx_tm_wday]
获取星期几的缩写
gmt.ngx_tm_wday
:0-6(0=Sunday)
week[]
:预定义的星期字符串数组(如["Sun", "Mon", ...])src\core\ngx_times.c 中:
static char *week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
gmt.ngx_tm_mday
获取月份中的日期(1-31)
months[gmt.ngx_tm_mon - 1]
获取月份缩写
gmt.ngx_tm_mon
:1-12(1=January)
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" };
gmt.ngx_tm_year
获取年份(绝对年份,如2023)
gmt.ngx_tm_hour
小时(0-23)
gmt.ngx_tm_min
分钟(0-59)
gmt.ngx_tm_sec
秒(0-59)
#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
在 我现在的 Ubuntu 环境下
#elif (NGX_HAVE_GMTOFF)
这个条件成立
所以
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
是包含本地时间的结构体(含时区信息)
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
结构体提取秒级时区偏移,转换为分钟单位- 将计算结果缓存到全局变量和当前时间结构体
NGX_HAVE_GETTIMEZONE
Ubuntu 下 nginx-1.24.0 源码分析 - NGX_HAVE_GETTIMEZONE 宏-CSDN博客
NGX_HAVE_GMTOFF
Ubuntu 下 nginx-1.24.0 源码分析 - NGX_HAVE_GMTOFF 宏-CSDN博客
ngx_localtime
函数Ubuntu 下 nginx-1.24.0 源码分析 - ngx_localtime 函数-CSDN博客
static ngx_int_t cached_gmtoff;
cached_gmtoff
是一个全局变量,其作用是缓存当前时间的时区偏移量(以分钟为单位)。这个变量的主要目的是优化性能,避免在每次调用ngx_time_update
函数时重复计算时区偏移量。
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
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);
将时间数据格式化为字符串并写入
格式字符串
"%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
:月份(范围1-12
,直接显示为01
到12
)。
tm.ngx_tm_mday
:日期(范围1-31
)。
tm.ngx_tm_hour
:小时(范围0-23
)。
tm.ngx_tm_min
:分钟(范围0-59
)。
tm.ngx_tm_sec
:秒(范围0-60
,考虑闰秒)输出示例:
2023/10/05 15:30:45
意图:
生成一个易读的本地时间字符串,用于错误日志的时间戳。
缓存此字符串,避免每次记录日志时重复格式化时间,提升性能。
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]
:月份缩写。
tm.ngx_tm_mon
是 1-based(1=一月),所以减 1 转换为 0-based 的数组索引。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
以分钟为单位的时区偏移示例输出
假设当前时间为 2023 年 10 月 2 日 14:05:09,时区为东八区(+8:00):
- 格式化结果:
02/Oct/2023:14:05:09 +0800
ngx_abs
src/core/ngx_core.h 中
#define ngx_abs(value) (((value) >= 0) ? (value) : - (value))
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)
:时区偏移的分钟部分(绝对值)。
示例输出
假设当前时间为 2023 年 10 月 5 日 15:30:45,时区为东八区(+08:00):
格式化结果:
2023-10-05T15:30:45+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 = &cached_syslog_time[slot][0];
将指针 `p4` 指向 `cached_syslog_time` 数组中当前 `slot` 对应的缓存位置的首地址。
`cached_syslog_time` 是一个二维数组,用于存储多个预生成的 syslog 时间字符串(通过轮换 `slot` 实现无锁更新)
(void) ngx_sprintf(p4, "%s %2d %02d:%02d:%02d", ...);
将格式化后的 syslog 时间字符串写入 `p4` 指向的缓存
格式字符串 "%s %2d %02d:%02d:%02d":
对应 `月份 日期 小时:分钟:秒`,例如 `Jan 5 08:05:03`。
- `%s`:3 字母月份缩写(如 `Jan`)。
- `%2d`:日期,固定 2 位宽度,单数日用空格填充(如 ` 5`)。
- `%02d`:时、分、秒,固定 2 位宽度,不足补零(如 `08:05:03`)。
参数列表:
- `months[tm.ngx_tm_mon - 1]`:从月份数组中获取缩写名称。
`tm.ngx_tm_mon` 应为 `1-12`,减 1 后适配数组索引 `0-11`(假设 `months` 为 `["Jan", "Feb", ...]`)。
- `tm.ngx_tm_mday`:月份中的日期(1-31)。
- `tm.ngx_tm_hour`、`tm.ngx_tm_min`、`tm.ngx_tm_sec`:本地时间的小时、分钟、秒。
ngx_memory_barrier
ngx_memory_barrier();
ngx_memory_barrier()
是内存屏障(Memory Barrier),用于解决多线程环境下的 内存可见性 和 指令重排序 问题:
- 内存可见性:确保当前线程对内存的修改对其他线程立即可见。
- 指令重排序:防止编译器或 CPU 将屏障前后的指令乱序执行。
在 Nginx 这种高并发场景中,时间更新可能被多个线程(如 Worker 进程)竞争访问,内存屏障能确保全局时间变量的更新是原子且一致的。
这是 gcc -E 之后的结果
__sync_synchronize();
定义在
src/os/unix/ngx_atomic.h 中:
#define ngx_memory_barrier() __sync_synchronize()
objs/ngx_auto_config.h:9:#define NGX_HAVE_GCC_ATOMIC 1
这个宏存在时成立
在C语言中,
__sync_synchronize()
是一个由GCC(GNU Compiler Collection)提供的内置函数,用于实现内存屏障(Memory Barrier)或 内存栅栏(Memory Fence) 。它的主要作用是确保在多线程环境中,编译器和处理器不会对指令进行重排序,从而保证内存访问的顺序性。
__sync_synchronize()
的作用(1) 对编译器的影响
编译器在优化代码时,可能会重排指令以提高性能
使用
__sync_synchronize()
后,编译器会确保在此屏障之前的指令不会被重排到屏障之后,反之亦然(2) 对处理器的影响
处理器也可能对指令进行重排。例如,在某些架构上,写操作可能会被延迟,或者读操作可能会被提前。
内存屏障会强制处理器按照程序的逻辑顺序执行屏障前后的内存操作,并确保屏障之前的所有写操作在屏障之后的读操作之前完成。
(3) 对缓存的影响
内存屏障会强制处理器将屏障之前的写操作刷新到主存,同时使其他核心上的缓存失效(如果它们缓存了相同的内存地址)。
这样,其他线程在读取该内存地址时,会从主存或更新后的缓存中获取最新的值,而不是使用旧的缓存值。
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;
1
.ngx_cached_time = tp;
作用:更新全局时间结构体指针。
tp
指向当前时间槽(cached_time[slot]
),其中已存储最新时间(sec
和msec
)。通过将
ngx_cached_time
指向tp
,所有读取时间的线程无需加锁即可获取最新时间。
2.
ngx_cached_http_time.data = p0;
作用:设置 HTTP 协议标准时间格式缓存。
p0
指向预格式化的 HTTP 时间字符串(如Date: Wed, 21 Feb 2024 03:04:05 GMT
)。格式符合 RFC 1123,用于 HTTP 响应头(如
Date
、Last-Modified
)。
3.
ngx_cached_err_log_time.data = p1;
作用:设置错误日志时间格式缓存。
4.
ngx_cached_http_log_time.data = p2;
作用:设置 HTTP 访问日志时间格式缓存。
5.
ngx_cached_http_log_iso8601.data = p3;
作用:设置 ISO 8601 标准时间格式缓存。
6.
ngx_cached_syslog_time.data = p4;
作用:设置 Syslog 时间格式缓存。
ngx_unlock(&ngx_time_lock);
释放锁
相关文章:
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_time_update函数
定义在 src\core\ngx_times.c 中 ngx_time_init 函数后面 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…...

老牌系统工具箱,现在还能打!
今天给大家分享一款超实用的电脑软硬件检测工具,虽然它是一款比较“资深”的软件,但依然非常好用,完全能满足我们的日常需求。 电脑软硬件维护检测工具 功能强大易用 这款软件非常贴心,完全不需要安装,直接打开就能用…...
mysql error1449解决方法
MySQL Error 1449 错误信息为 “The user specified as a definer (userhost) does not exist”,意思是定义者(创建存储过程、函数、触发器等数据库对象时指定的用户)在当前系统中不存在,从而导致无法正常使用这些对象。以下是针对…...

Notepad++ 中删除所有以 “pdf“ 结尾的行
Notepad 中删除所有以 “pdf” 结尾的行 操作步骤 1.打开文件: 在 Notepad 中打开你需要处理的文本文件。 2.打开查找和替换对话框: 按快捷键 Ctrl F,打开“查找和替换”对话框。 3.启用正则表达式模式: 在对话框的底部…...

归并排序 和 七大算法的总结图
目录 什么是递归排序: 图解: 递归方法: 代码实现: 思路分析: 非递归方法: 思路: 代码实现: 思路分析: 什么是递归排序: 先将数据分解成诺干个序列࿰…...
嵌入式硬件篇---原码、补码、反码
文章目录 前言简介八进制原码、反码、补码1. 原码规则示例问题 2. 反码规则示例问题 3. 补码规则示例优点 4. 补码的运算5. 总结 十六进制原码、反码、补码1. 十六进制的基本概念2. 十六进制的原码规则示例 3. 十六进制的反码规则示例 4. 十六进制的补码规则示例 5. 十六进制补…...

评估多智能体协作网络(MACNET)的性能:COT和AUTOGPT基线方法
评估多智能体协作网络(MACNET)的性能 方法选择:选择COT(思维链,Chain of Thought)、AUTOGPT等作为基线方法。 COT是一种通过在推理过程中生成中间推理步骤,来增强语言模型推理能力的方法,能让模型更好地处理复杂问题,比如在数学问题求解中,展示解题步骤。 AUTOGPT则是…...
洛谷题目: P2398 GCD SUM 题解 (本题较难,省选-难度)
题目传送门: P2398 GCD SUM - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 前言: 本题涉及到 欧拉函数,素数判断,质数,筛法 ,三大知识点,相对来说还是比较难的。 本题要求我们计算 …...

kubernetes-cni 框架源码分析
深入探索 Kubernetes 网络模型和网络通信 Kubernetes 定义了一种简单、一致的网络模型,基于扁平网络结构的设计,无需将主机端口与网络端口进行映射便可以进行高效地通讯,也无需其他组件进行转发。该模型也使应用程序很容易从虚拟机或者主机物…...
AI Agent有哪些痛点问题
AI Agent有哪些痛点问题 目录 AI Agent有哪些痛点问题AI Agent领域有哪些知名的论文缺乏一个将智能多智能体技术和在真实环境中学习的两个适用流程结合起来的统一框架LLM的代理在量化和客观评估方面存在挑战自主代理在动态环境中学习、推理和驾驭不确定性存在挑战AI Agent领域有…...
使用Java爬虫获取京东JD.item_sku API接口数据
在电商领域,商品的SKU(Stock Keeping Unit)信息是运营和管理的关键数据。SKU信息包括商品的规格、价格、库存等,对于商家的库存管理、定价策略和市场分析至关重要。京东作为国内领先的电商平台,提供了丰富的API接口&am…...

华为云+硅基流动使用Chatbox接入DeepSeek-R1满血版671B
华为云硅基流动使用Chatbox接入DeepSeek-R1满血版671B 硅基流动 1.1 注册登录 1.2 实名认证 1.3 创建API密钥 1.4 客户端工具 OllamaChatboxCherry StudioAnythingLLM 资源包下载: AI聊天本地客户端 接入Chatbox客户端 点击设置 选择SiliconFloW API 粘贴1.3创…...
平方数列与立方数列求和的数学推导
先上结论: 平方数列求和公式为: S 2 ( n ) n ( n 1 ) ( 2 n 1 ) 6 S_2(n) \frac{n(n1)(2n1)}{6} S2(n)6n(n1)(2n1) 立方数列求和公式为: S 3 ( n ) ( n ( n 1 ) 2 ) 2 S_3(n) \left( \frac{n(n1)}{2} \right)^2 S3(n)(2n(n1)…...
Java中的synchronized关键字与锁升级机制
在多线程编程中,线程同步是确保程序正确执行的关键。当多个线程同时访问共享资源时,如果不进行同步管理,可能会导致数据不一致的问题。为了避免这些问题,Java 提供了多种同步机制,其中最常见的就是 synchronized 关键字…...

告别传统校准!GNSS模拟器在计量行业的应用
随着GNSS技术的不断进步,各类设备广泛采用该技术实现高精度定位,并推动了其在众多领域的广泛应用。对于关键行业如汽车制造和基础设施,设备的可用性和可靠性被视为基本准则,GNSS作为提供“绝对位置”信息的关键传感器,…...

数据结构结尾
1.二叉树的分类 搜索二叉树,平衡二叉树,红黑树,B树,B树 2.Makefile文件管理 注意: 时间戳:根据时间戳,只编译发生修改后的文件 算法: 算法有如上五个要求。 算法的时间复杂度&am…...

【golang】量化开发学习(一)
均值回归策略简介 均值回归(Mean Reversion)假设价格会围绕均值波动,当价格偏离均值一定程度后,会回归到均值。 基本逻辑: 计算一段时间内的移动均值(如 20 天均线)。当当前价格高于均值一定比…...

AI前端开发:跨领域合作的新引擎
随着人工智能技术的飞速发展,AI代码生成器等工具的出现正深刻地改变着软件开发的模式。 AI前端开发的兴起,不仅提高了开发效率,更重要的是促进了跨领域合作,让数据科学家、UI/UX设计师和前端工程师能够更紧密地协同工作࿰…...

数组练习(深入理解、实践数组)
1.练习1:多个字符从两端移动,向中间汇聚 编写代码,演示多个字符从两端移动,向中间汇聚 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<string.h> int main() {//解题思路://根据题意再…...

Bigemap Pro如何进行面裁剪
一般在处理矢量数据,制图过程中,常常会用到面文件的裁剪功能,那么有没有一个工具可以同时实现按照线、顶点、网格以及面来裁剪呢?今天给大家介绍一个宝藏工具,叫做Bigemap Pro,在这里工具里面可以实现上述面…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...

FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...