我们在程序中会频繁地取当前时间,例如处理一个http请求时,两次调用gettimeofday取差值计算出处理该请求消耗了多少秒。这样的调用无处不在,所以我们有必要详细了解下,gettimeofday这个函数做了些什么?内核1ms一次的时钟中断处理真的可以支持tv_usec字段达到微秒精度吗?它的调用成本在i386/x86_64体系架构上代价一样吗?如果在系统繁忙时,频繁的调用它有问题吗?
gettimeofday是C库提供的函数(不是系统调用),它封装了内核里的sys_gettimeofday系统调用,就是说,归根到底是系统调用。
但是,内核对于x86_64体系结构下,除了普通的系统调用外,还提供了sysenter和vsyscall方式来获取内核态的数据。目前我们使用的操作系统大都是x86_64体系的,如果我们用strace命令跟踪,就会发现gettimeofday命令实际上没有执行系统调用(i386体系会有),这是因为:x86_64体系上,使用vsyscall实现了gettimeofday这个系统调用。具体就是,创建了一个共享的内存页面,它是在内核态的,它的数据由内核来维护,但是,用户态也有权限访问这个内核页面,由此,不通过中断gettimeofday也就拿到了系统时间。
接下来,我来详细回答以上4个问题。
一、gettimeofday做了些什么?
它把内核保存的墙上时间和jiffies综合处理后返回给用户。解释下墙上时间和jiffies是什么:1、墙上时间就是实际时间(1970/1/1号以来的时间),它是由我们主板电池供电的(装过PC机的同学都了解)RTC单元存储的,这样即使机器断电了时间也不用重设。当操作系统启动时,会用这个RTC来初始化墙上时间,接着,内核会在一定精度内根据jiffies维护这个墙上时间。2、jiffies就是操作系统启动后经过的时间,它的单位是节拍数。有些体系架构,1个节拍数是10ms,但我们常用的x86体系下,1个节拍数是1ms。也就是说,jiffies这个全局变量存储了操作系统启动以来共经历了多少毫秒。我们来看看gettimeofday是如何做的。首先它调用了sys_gettimeofday系统调用。
asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz)
{
if (likely(tv != NULL)) {
struct timeval ktv;
do_gettimeofday(&ktv);
if (copy_to_user(tv, &ktv, sizeof(ktv)))
return -EFAULT;
}
if (unlikely(tz != NULL)) {
if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))
return -EFAULT;
}
return 0;
}
大家看到,它调用do_gettimeofday函数取到当前时间存储到局部变量ktv上,然后调用copy_to_user把结果复制到用户空间。每个体系都有自己的实现,我这里就简单列下x86_64体系下do_gettimeofday的实现:
void do_gettimeofday(struct timeval *tv)
{
unsigned long seq, t;
unsigned int sec, usec;
do {
seq = read_seqbegin(&xtime_lock);
sec = xtime.tv_sec;
usec = xtime.tv_nsec / 1000;
/* i386 does some correction here to keep the clock
monotonous even when ntpd is fixing drift.
But they didn't work for me, there is a non monotonic
clock anyways with ntp.
I dropped all corrections now until a real solution can
be found. Note when you fix it here you need to do the same
in arch/x86_64/kernel/vsyscall.c and export all needed
variables in vmlinux.lds. -AK */
t = (jiffies - wall_jiffies) * (1000000L / HZ) +
do_gettimeoffset();
usec += t;
} while (read_seqretry(&xtime_lock, seq));
tv->tv_sec = sec + usec / 1000000;
tv->tv_usec = usec % 1000000;
}
大家看到,只是把xtime加以jiffies修正后返回给用户而已。而xtime变量和jiffies的维护更新频率,就决定了时间精度,上面说了,每10或者1ms才处理一次时钟中断,难道精度只到1ms吗?继续往下。
二、内核1ms一次的时钟中断真的可以支持tv_usec字段达到微秒精度吗?
可以,因为这个时间还会由High Precision Event Timer来维护,这个模块会处理微秒级的中断,并更新xtime和jiffies变量。我们看下x86_64体系结构下的维护代码:
static struct irqaction irq0 = {
timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL
};
这个timer_interrupt函数会处理HPET时间中断,来更新xtime变量。
三、它的调用成本在所有的操作系统上代价一样吗?如果在系统繁忙时,1毫秒内调用多次有问题吗?
最上面已经说了,对于x86_64系统来说,这是个虚拟系统调用vsyscall!所以,这里它不用发送中断!速度很快,成本低,调用一次的成本大概不到一微秒!
对于i386体系来说,这就是系统调用了!最简单的系统调用都有无法避免的成本:陷入内核态。当我们调用gettimeofday时,将会向内核发送软中断,然后将陷入内核态,这时内核至少要做下列事:处理软中断、保存所有寄存器值、从用户态复制函数参数到内核态、执行、将结果复制到用户态。这些成本至少在1微秒以上!
四、关于jiffies值得一提的两点
先看看它的定义:
volatile unsigned long __jiffies;
只谈两点。
1、它用了一个C语言里比较罕见的关键字volatile,这个关键字用于解决并发问题。C语言编译器很喜欢做优化的,它不清楚某个变量可能会被并发的修改,例如上面的jiffies变量首先是0,如果首先一个CPU修改了它的值为1,紧接着另一个CPU在读它的值,例如 __jiffies = 0; while (__jiffies == 1),那么在内核的C代码中,如果不加volatile字段,那么第二个CPU里的循环体可能不会被执行到,因为C编译器在对代码做优化时,生成的汇编代码不一定每次都会去读内存!它会根据代码把变量__jiffies设为0,并一直使用下去!而加了volatile字段后,就会要求编译器,每次使用到__jiffies时,都要到内存里真实的读取这个值。
2、它的类型是unsigned long,在32位系统中,最大值也只有43亿不到,从系统启动后49天就到达最大值了,之后就会清0重新开始。那么jiffies达到最大值时的回转问题是怎么解决的呢?或者换句话说,我们需要保证当jiffies回转为一个小的正数时,例如1,要比几十秒毫秒前的大正数大,例如4294967290,要达到jiffies(1)>jiffies(4294967290)这种效果。
内核是通过定义了两个宏来解决的:
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(b) - (long)(a) < 0))
#define time_before(a,b) time_after(b,a)
很巧妙的设计!仅仅把unsigned long转为long类型后相减比较,就达到了jiffies(1)>jiffies(4294967290)效果,简单的解决了jiffies的回转问题,赞一个。
分享到:
相关推荐
Linux中的gettimeofday()函数是一个用于获取当前时间的系统调用,该函数能够提供精确到微秒的时间值。在C语言的开发环境中,尤其是Linux系统编程中,gettimeofday()函数经常被使用以获取准确的时间信息。了解并掌握...
gettimeofday 取得目前的时间例子
这个函数可以提供高精度的时间测量,对于计算程序执行的耗时,尤其是在性能分析和优化中,是极其有用的工具。 `gettimeofday`函数的原型如下: ```c #include int gettimeofday(struct timeval *tv, struct time...
在C语言中,`settimeofday()`和`gettimeofday()`函数是与时间操作相关的系统调用,主要用于获取和设置系统的当前时间。这两个函数都是在 `<sys/time.h>` 头文件中定义,并且通常需要包含 `<unistd.h>` 头文件来使用...
`gettimeofday`函数是Linux提供的一种方法,用于获取系统当前的时间,从而计算出程序段的执行时间。这个函数定义在`sys/time.h`头文件中,其原型如下: ```c int gettimeofday(struct timeval *tv, struct timezone...
6. **gettimeofday函数**: - `gettimeofday`函数提供了比`time`更精确的时间获取,它返回的是微秒级别的精度。函数原型为`int gettimeofday(struct timeval *tv, struct timezone *tz)`。`tv`参数用于存储当前时间...
本文将详细介绍C语言中用于获取时间的几种常用函数,包括`asctime()`、`ctime()`、`gettimeofday()`和`gmtime()`等,并通过实例代码展示它们的具体用法。 #### 1. `asctime()` - **函数定义**:`char * asctime...
在Linux系统编程中,`gettimeofday`和`xtime`是两个重要的时间相关函数,用于获取当前系统的精确时间。这两个函数在很多场景下都发挥着关键作用,例如计时、性能分析、时间戳记录等。下面我们将深入探讨它们的使用...
- gettimeofday()函数用于获取当前时间的高精度值(tv_sec和tv_usec)。tv_sec表示自1970年1月1日以来的秒数,tv_usec表示微秒数。tz结构体用于存储时区信息,包括距格林尼治的分钟差(tz_minuteswest)和夏令时的...
该函数用通过调用gettimeofday分类统计了机器的指令周期反应时间。精度到微秒。
gettimeofday()函数可以返回当前时间的详细数组信息,包含微秒等;microtime()函数返回当前时间的时间戳和微秒数;strtotime()函数将任何英文文本的日期时间描述解析为Unix时间戳;mktime()函数用于根据给定日期和...
3. **gettimeofday函数**: `gettimeofday`提供了更精确的时间获取,它能够获取到微秒级别的当前时间。这个函数返回一个`timeval`结构体,包含了秒数和微秒数。同时,如果需要,还可以获取到与格林尼治标准时间的...
以上代码利用了`gettimeofday()`函数获取高精度时间,然后根据需求转换为毫秒或微秒。请注意,实际的实现可能会有所不同,具体取决于目标硬件和操作系统的特性。 在实际应用中,这些接口函数可以被广泛使用。例如,...
`gettimeofday`是Linux中一个用于获取当前时间的函数,但在Windows系统中并不提供。因此,我们需要为Windows编写一个替换函数来实现相同的功能。本文将详细介绍如何在Windows下替换`gettimeofday`函数。 `...
C/C++时间函数的使用是编程中常见的需求,特别是在处理定时任务、性能分析或日志记录时。在C和C++中,我们通常利用`<ctime>`或`<time.h>`头文件提供的函数来操作时间。本文将详细介绍如何在C/C++中获取、操作和显示...
3. gettimeofday函数: `gettimeofday()` 函数用于获取当前系统时间,提供更高的精度,可以获取到微秒级别的时间。函数原型为: ```c int gettimeofday(struct timeval *tv, struct timezone *tz); ``` 它将当前时间...