获取系统时间的一般方法
在Linux平台上获取系统时间的方法有很多,比如使用time()函数、gettimeofday()函数和localtime()函数等,大部分函数本质上都是通过系统调用来获取时间的。但是使用系统调用时,有一个问题需要特别关注,就是系统调用开销的问题。
系统调用的开销
Nginx服务器程序在获取系统时间时,本质上使用的是gettimeofday函数,该函数是C语言库提供的函数,从严格意义上来讲,该函数不也是系统调用,但它是对系统调用sys_gettimeofday()的封装,因此一般也就认为它是一个系统调用了。
大家都知道,Linux平台上程序函数调用可以分为库调用和系统调用两大类,这里不过多的解释这两种机制的不同,主要是明确系统调用与库调用相比,时间成本是相当巨大的。程序执行一次系统调用将至少经过一下步骤:
- 应用程序调用库函数API
- API将系统调用号存入寄存器EAX,然后通过中断调用使系统进入内核态(陷入内核空间)。
- 内核中的中断处理函数根据寄存器EAX中的系统调用号,调用对应的内核函数(系统调用)。
- 系统调用完成相应功能,将返回值存入寄存器EAX,返回到中断处理函数。
- 中断处理函数返回到API中。
- API将寄存器EAX返回给应用程序。
我们可以看到,在执行系统调用的过程中要涉及内核空间和用户空间转换、系统中断处理等过程,这些都增加了大量的系统开销。在实际进行程序设计的过程中,如果不注意减少系统调用的使用,应用程序将无法得到很好的执行效率。
Nginx服务器在设计之初,主要关注的一方面就是效率问题,并且从实际的使用情况来看,Nginx服务器的系统开销非常低,而运行效率也是十分令人满意的,由此可以推断出,它在对系统调用的问题上,处理方案是谨慎和有效的。为了获取精确的系统时间,Nginx服务器使用了gettimeofday()系统调用。那么,Nginx服务器是怎么做到既使用系统调用获取精确时间,又保证程序的运行效率和系统开销的呢?
gettimeofday()
在Linux平台上使用C语言进行程序设计时,为了精确获取系统时间,我们经常会使用gettimeofday()这个函数。该函数的原型为:
#include <sys/time.h>
int gettimeofday(struct timeval *tv,struct timezone *tz)
调用该函数后,当前的时间将用timeval结构体返回,时间可以精确到微秒。当地时区的信息则放在timezone结构体中。timeval结构体的定义为:
struct timeval {
long tv_sec;
long tv_usec;
}
gettimeofday()函数在执行过程中,一般情况下实际上是调用了另一个函数sys_gettimeofday(),该函数才是名副其实的系统调用,通过该调用就可以获取保存在系统内核中的时间信息。在实际程序设计中是不提倡频繁使用gettimeofday()函数的。
vsyscall方式在内存中创建了一个内核态的共享页面,它的数据由内核来维护,但这块区域用户态也有权限访问,通过这样的机制,不经过系统中断和陷入内核也能获取一些内核信息。x86_64体系上使用vsyscall方式实现了gettimeofday()的功能,这样系统开销比普通的系统调用要小的多。
Nginx时间管理的工作原理
Nginx服务器重视程序的高效运行,Nginx程序采取缓存时间的方法来减少对gettimeofday()的调用,并且每个工作进程会自行维护时间缓存。Nginx的时间缓存一般会赋值给一下四种变量,更新缓存时是同步更新的。
- ngx_cached_http_time
- ngx_cached_err_log_time
- ngx_cached_http_log_time
- ngx_cached_http_log_iso8601
既然是对时间做了缓存,就必须有相关的程序来完成更新缓存的工作。Nginx程序中有两个相关函数在不同的情况下来完成这个工作。
时间缓存的更新
Nginx服务器更新时间缓存的两个函数是ngx_time_update()和ngx_time_sigsafe_update(),具体的实现的源码都在/nginx/src/core/ngx_time.c中可以找到。我们分别来分析一下它们的源码。
1.ngx_time_update()
该函数是Nginx服务器时间管理的核心函数。更新时间缓存的过程实际上是一个写缓存的过程,Nginx服务器为了解决信号处理过程中更新时间缓存产生的数据一致性的问题,需要使用原子变量ngx_time_lock进行写加锁。
2.ngx_time_update()的调用
该函数的调用时机主要有三个:一是在Nginx服务器主进程捕捉、处理完一个信号返回的时候。二是在缓存索引管理进程中调用该函数,用于标记缓存数据的时间属性。三是在Nginx服务器工作进程中在进行事件处理时调用了该函数。主要是第三种情况对该函数的调用频率较高。
epoll_wait()函数用于等待事件的产生,执行epoll_wait()函数返回后会调用ngx_time_update()函数更新时间缓存。当epoll机制通知有事件到达或者epoll机制超时退出时,Nginx服务器程序就会更新一次缓存时间。然后调用各个事件对应的处理函数处理事件。
缓存时间的精度
设置缓存时间的精度
指令timer_resolution用于设置执行两次缓存时间更新工作之间的间隔时间。该参数设置的越大,则对系统调用gettimeofday()的使用频率就越低,但缓存时间的精度也就越低。该指令的语法结构为:
timer_resolution interval;
其中的Interval参数就是更新时间间隔,默认值为100ms。该指令只能在Nginx配置文件的全局块中进行设置。