找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 21291|回复: 0

[原]展开了谈谈时间函数gettimeofday

[复制链接]
发表于 2012-1-11 16:30:45 | 显示全部楼层 |阅读模式
我们在程序中会频繁地取当前时间,例如处理一个http请求时,两次调用gettimeofday取差值计算出处理该请求消耗了多少秒。这样的调用无处不在,所以我们有必要详细了解下,gettimeofday这个函数做了些什么?它可以精确到微秒吗?它的成本有多大?如果在系统繁忙时,频繁的调用它有问题吗?

gettimeofday毫无疑问是C库提供的函数,然后这个函数却是封装了sys_gettimeofday系统调用,就是说,执行这个函数,必然执行一次系统调用。接下来,我来试着回答以上4个问题。


一、gettimeofday做了些什么?
它把内核保存的墙上时间和jiffies综合处理后返回给用户。解释下墙上时间和jiffies是什么:1、墙上时间就是实际时间(1970/1/1号以来的时间),它是由我们主板电池供电的(装过PC机的同学都了解)RTC单元存储的,这样即使机器断电了时间也不用重设。当操作系统启动时,会用这个RTC来初始化墙上时间,接着,内核会在一定精度内根据jiffies维护这个墙上时间。2、jiffies就是操作系统启动后经过的时间,它的单位是节拍数。有些体系架构,1个节拍数是10ms,但我们常用的x86体系下,1个节拍数是1ms。也就是说,jiffies这个全局变量存储了操作系统启动以来共经历了多少毫秒。我们来看看gettimeofday是如何做的。首先它调用了sys_gettimeofday系统调用。
  1. asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz)
  2. {
  3.         if (likely(tv != NULL)) {
  4.                 struct timeval ktv;
  5.                 do_gettimeofday(&ktv);
  6.                 if (copy_to_user(tv, &ktv, sizeof(ktv)))
  7.                         return -EFAULT;
  8.         }
  9.         if (unlikely(tz != NULL)) {
  10.                 if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))
  11.                         return -EFAULT;
  12.         }
  13.         return 0;
  14. }
复制代码
大家看到,它调用do_gettimeofday函数取到当前时间存储到局部变量ktv上,然后调用copy_to_user把结果复制到用户空间。每个体系都有自己的实现,我这里就简单列下x86_64体系下do_gettimeofday的实现:void do_gettimeofday(struct timeval *tv)
  1. {
  2.         unsigned long seq, t;
  3.         unsigned int sec, usec;
  4.         do {
  5.                 seq = read_seqbegin(&xtime_lock);
  6.                 sec = xtime.tv_sec;
  7.                 usec = xtime.tv_nsec / 1000;
  8.                 /* i386 does some correction here to keep the clock
  9.                    monotonous even when ntpd is fixing drift.
  10.                    But they didn't work for me, there is a non monotonic
  11.                    clock anyways with ntp.
  12.                    I dropped all corrections now until a real solution can
  13.                    be found. Note when you fix it here you need to do the same
  14.                    in arch/x86_64/kernel/vsyscall.c and export all needed
  15.                    variables in vmlinux.lds. -AK */
  16.                 t = (jiffies - wall_jiffies) * (1000000L / HZ) +
  17.                         do_gettimeoffset();
  18.                 usec += t;
  19.         } while (read_seqretry(&xtime_lock, seq));
  20.         tv->tv_sec = sec + usec / 1000000;
  21.         tv->tv_usec = usec % 1000000;
  22. }
复制代码
二、gettimeofday可以精确到微秒吗?

不可以,上面说了,它最终的精确是由jiffies全局变量决定的,而jiffies的单位是节拍数,节拍数又是跟体系结构对应的。目前,x86-64的节拍数是1ms,所以,我们取到的当前时间精度只到毫秒,而且会有正负0.5毫秒的误差。

那么,为什么不能精确到微秒或者纳秒呢?难道cpu现在号称nGHZ都是假的吗?
呵呵,是真的,但是时钟的维护不可能精确到指令级的。时钟就是靠jiffies变量维护的,jiffies是通过时钟中断来维护的。所以,当我们需要更精确的时间精度时,就意味着在一秒钟会有更多的时钟中断需要内核处理,这会加大操作系统的负担。以前,是10ms一个节拍数,意味着时间精度只到10ms。现在随着每秒钟CPU可以处理更多的指令,大部分体系架构下,都是1ms一个节拍数,这意味着目前大家觉得,1秒钟发送1000个时钟中断最平衡。每个时钟中断来临时,中断处理程序会更新jiffies这个变量。

三、它的成本有多大?如果在系统繁忙时,几毫秒调用一次有问题吗?
最上面已经说了,这是个系统调用!最简单的系统调用都有无法避免的成本:陷入内核态。当我们调用gettimeofday时,将会向内核发送软中断,然后将陷入内核态,这时内核至少要做下列事:处理软中断、保存所有寄存器值、从用户态复制函数参数到内核态、执行、将结果复制到用户态。这些都是成本!
而且,它本身的精度是1ms处理一次CPU的时钟中断,精度也只到毫秒,如果我们只是几毫秒就调用一次,就有点得不偿失了。所以,当我们的代码中在运行时非常频繁的调用gettimeofday时,请思考下,是否每次都有必要?是否需要缓存下这个值在用户空间呢?学学nginx或者JVM吧。

四、关于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的回转问题,赞一个。

作者:russell_tao 发表于2012-1-9 14:14:17 原文链接

您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-5-8 07:43 , Processed in 0.188987 second(s), 7 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表