(nginx源码系列三)--nginx时间缓存分析
Why
nginx把整个时间的系统调用都封装了一遍。
其中有两个原因
1 不谈其他平台,至少在linux上,很多时间相关的系统调用不是async-signal-safe或thread-safe的。
例如local_time_r虽然是thread-safe的但不是async-signal-safe的,当然local_time两个都不是。
这里不是专门谈什么是async-siganl-safe的,对此概念上有疑惑或者错用后危害上有疑惑的可以点这个博文。写的相当透彻。
这篇文章主要是讲twemproxy遇到的一个死锁BUG(我在线上也遇到这样的问题了),连twitter的明星coder都会犯这个错误。可见还是需要注意这一点的。
twemproxy-deadlock-on-signal_handler
2 另外则是减少系统调用的耗时,毕竟web server不是那么要求时间的精确性,但是如果有场合需要,nginx还是提供了参数来控制时间的精确性。
How
先看大逻辑再看细节吧。
大逻辑
前文已经分析过nginx的启动流程的。
可以看到时间缓存的更新是在ngx_epoll_process_events函数里面的
1 | static ngx_int_t |
当flag为NGX_UPDATE_TIME或者ngx_event_timer_alarm不为0时进行时间更新。
这分别代表两个策略。
1 | void |
策略一:当未设置ngx_timer_resolution时直接每一次调用后都更新时间
策略二:当设置了ngx_timer_resolution后会如何呢?
1 | static ngx_int_t |
可以看到,在最开始事件循环初始化时设置了一个时间信号函数,每隔ngx_timer_resolution时间进行触发
触发函数把ngx_event_timer_alarm设置为1
随后当epoll被信号触发后不在阻塞立刻返回,errno设置为EINTR
随后进行时间更新
时间更新的实现
再看core/ngx_times.c里核心函数ngx_time_update的实现
1 | void |
可以看到这里考虑到了很多问题。
比如使用了NGX_TIME_SLOTS大的缓存数组,来进行更新,这是为了防止多线程下或者信号函数读时,这里正在进行更新操作而导致不一致。
目前并未使用多线程机制,所以64这个值已经很大了,我认为使用2也可以同样满足需求。
另外ngx_memory_barrier内存屏障,是个宏
1 |
当然在不同平台下有不同实现,总得来说都是让编译器不要优化这一段合并到上面去,让p0,p1,p2,p3的值一起更新到time_cache中去。
编译器可能会优化成这样的顺序:
1 | p0 = &cached_http_time[slot][0]; |
加了编译器级别的内存屏障以后,在多线程情况下。
能够使得大部分情况下p0,p1,p2,p3都是代表同一时刻的值。
极小部分情况下当前p0值的代表的时刻可能比p1,p2,p3代表的新。
这里细节处理的很厉害。
顺带提一下ngx_times.c的其他函数
ngx_time_init时间初始化
ngx_time_sigsafe_update当信号切入时进行时间更新,只更新了ngx_cached_err_log_time
ngx_http_time和ngx_http_cookie_time,http和http_cookie的时间格式化,调用了ngx_gmtime
ngx_gmtime(time_t t, ngx_tm_t *tp)使用了自己的算法来进行时间转换
time_t ngx_next_time(time_t when) 这里when参数只是代表了当天的时间,只有秒+分钟+小时,顾名思义,寻找下一次这个时间点出现的时候的绝对时间time_t值
比如已经当前已经过了12点0分0秒,那么返回的就是下一天的12点0分0秒的,从标准计时点(一般是1970年1月1日午夜)到当前时间的秒数。否则则是当天的。
-
2024-12-02
本篇总结一下信号的必要知识,以及实际场景下的处理,主要参考UNIX环境高级编程(第三版)
要先从子进程fork开始总结,因为信号和子进程息息相关
wait系统调用
根据书中8.3节 fork以及8.6节 wait
- fork出子进程以后,假如子进程退出,需要用wait或者waitpid检查子进程的退出状态,否则子进程会进入僵死状态,直到父进程退出,被系统的1号进程接管进行wait才会释放。
- 父进程可以直接wait等待,也可以啥也不做,在SIGCHLD信号处理函数中,再进行wait调用
- waitpid比wait多一些功能,最主要的就是可以在第二个参数传入WNOHANG进行非阻塞调用