linux墙上时间xtime与高精度时钟gettimeofday

11360阅读 3评论2012-03-01 jinxinxin163
分类:LINUX

clocksource定义了一个clock device的基本属性及行为, 这些clock device一般都有计数, 定时, 产生中断能力。
  1. struct clocksource {
  2.     /*
  3.      * First part of structure is read mostly
  4.      */
  5.     char *name;
  6.     struct list_head list;
  7.     int rating;
  8.     cycle_t (*read)(struct clocksource *cs);
  9.     int (*enable)(struct clocksource *cs);
  10.     void (*disable)(struct clocksource *cs);
  11.     cycle_t mask;
  12.     u32 mult;
  13.     u32 mult_orig;
  14.     u32 shift;
  15.     unsigned long flags;
  16.     cycle_t (*vread)(void);
  17.     void (*resume)(void);
  18.     cycle_t cycle_interval;
  19.     u64 xtime_interval;
  20.     u32 raw_interval;
  21.     cycle_t cycle_last ____cacheline_aligned_in_smp;
  22.     u64 xtime_nsec;
  23.     s64 error;
  24.     struct timespec raw_time;
  25.     ...
  26. };

最重要的成员是read(), cycle_last和cycle_interval.
read()定义了读取clock device count 寄存器当前计数值接口
cycle_last保存上一次周期(时钟中断)计数值,它会在update_wall_time()得到更新,且以cycle_interval为基数增加:clock->cycle_last += clock->cycle_interval;
cycle_interval定义了“每个jiffies对应的计数值”
以1128为例,它使用ixp4xx的cpu,HZ=100,clocksource 为66.6666Mhz,那么这个值为666667(0xa2c2b)
通过在内核里打印这个变量的值,也验证了这个结果
这个结构内的值, 无论是cycle_t, 还是u64类型(实际cycle_t就是u64)都是时钟的计数值(cycle)
内核会默认注册一个jiffies的clocksource, 如下:

kernel/time/jiffies.c
  1. static cycle_t jiffies_read(struct clocksource *cs)
  2. {
  3.     return (cycle_t) jiffies;
  4. }

  5. struct clocksource clocksource_jiffies = {
  6.     .name = "jiffies",
  7.     .rating = 1, /* lowest valid rating*/
  8.     .read = jiffies_read,
  9.     .mask = 0xffffffff, /*32bits*/
  10.     .mult = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */
  11.     .mult_orig = NSEC_PER_JIFFY << JIFFIES_SHIFT,
  12.     .shift = JIFFIES_SHIFT,
  13. };
  14. static int __init init_jiffies_clocksource(void)
  15. {
  16.     return clocksource_register(&clocksource_jiffies);
  17. }

注册jiffies之后,内核会提供精度为1/HZ的时间精度,但是你的设备体系还会注册它自己的clocksource,这个clocksource是硬件相关的,会更精准,例如:

arch/arm/mach-ixp4xx/common.c
  1. cycle_t ixp4xx_get_cycles(struct clocksource *cs)
  2. {
  3.     return *IXP4XX_OSTS;
  4. }

  5. static struct clocksource clocksource_ixp4xx = {
  6.     .name = "OSTS",
  7.     .rating = 200,
  8.     .read = ixp4xx_get_cycles,
  9.     .mask = CLOCKSOURCE_MASK(32),
  10.     .shift = 20,
  11.     .flags = CLOCK_SOURCE_IS_CONTINUOUS,
  12. };
  13. unsigned long ixp4xx_timer_freq = FREQ;
  14. static int __init ixp4xx_clocksource_init(void)
  15. {
  16.     clocksource_ixp4xx.mult =
  17.         clocksource_hz2mult(ixp4xx_timer_freq,
  18.                     clocksource_ixp4xx.shift);
  19.     clocksource_register(&clocksource_ixp4xx);

  20.     return 0;
  21. }
  22. #define FREQ 66666666

rating高的clocksource会被优先使用,所以66.66Mhz的时钟源被优先使用,一个计数大概相当于15ns,所以,理论上提供15ns级的精度。

linux内核里的墙上时钟 xtime
  1. struct timespec xtime __attribute__ ((aligned (16)));
  2. struct timespec {
  3.     __kernel_time_t tv_sec; /* seconds */
  4.     long tv_nsec; /* nanoseconds */
  5. };

xtime的更新:
你的时钟中断处理函数->timer_tick->update_wall_time()
在update_wall_time里会更新墙上时间xtime
  1. void update_wall_time(void)
  2. {
  3.     cycle_t offset;

  4.     /* Make sure we're fully resumed: */
  5.     if (unlikely(timekeeping_suspended))
  6.         return;

  7. #ifdef CONFIG_GENERIC_TIME
  8.     offset = (clocksource_read(clock) - clock->cycle_last) & clock->mask;
  9. #else
  10.     offset = clock->cycle_interval;
  11. #endif
  12.     clock->xtime_nsec = (s64)xtime.tv_nsec << clock->shift;
  13.         printk(KERN_INFO "offset is %llx, interval is %llx\n", offset, clock->cycle_interval);//测试用
  14.     /* normally this loop will run just once, however in the
  15.      * case of lost or late ticks, it will accumulate correctly.
  16.      */
  17.     while (offset >= clock->cycle_interval) {
  18.         /* accumulate one interval */
  19.         offset -= clock->cycle_interval;
  20.         clock->cycle_last += clock->cycle_interval;

  21.         clock->xtime_nsec += clock->xtime_interval;
  22.         if (clock->xtime_nsec >= (u64)NSEC_PER_SEC << clock->shift) {
  23.             clock->xtime_nsec -= (u64)NSEC_PER_SEC << clock->shift;
  24.             xtime.tv_sec++;
  25.             second_overflow();
  26.         }

  27.         clock->raw_time.tv_nsec += clock->raw_interval;
  28.         if (clock->raw_time.tv_nsec >= NSEC_PER_SEC) {
  29.             clock->raw_time.tv_nsec -= NSEC_PER_SEC;
  30.             clock->raw_time.tv_sec++;
  31.         }

  32.         /* accumulate error between NTP and clock interval */
  33.         clock->error += tick_length;
  34.         clock->error -= clock->xtime_interval << (NTP_SCALE_SHIFT - clock->shift);
  35.     }

  36.     /* correct the clock when NTP error is too big */
  37.     clocksource_adjust(offset);

  38.     if (unlikely((s64)clock->xtime_nsec < 0)) {
  39.         s64 neg = -(s64)clock->xtime_nsec;
  40.         clock->xtime_nsec = 0;
  41.         clock->error += neg << (NTP_SCALE_SHIFT - clock->shift);
  42.     }

  43.     /* store full nanoseconds into xtime after rounding it up and
  44.      * add the remainder to the error difference.
  45.      */
  46.     xtime.tv_nsec = ((s64)clock->xtime_nsec >> clock->shift) + 1;
  47.     clock->xtime_nsec -= (s64)xtime.tv_nsec << clock->shift;
  48.     clock->error += clock->xtime_nsec << (NTP_SCALE_SHIFT - clock->shift);

  49.     update_xtime_cache(cyc2ns(clock, offset));

  50.     /* check to see if there is a new clocksource to use */
  51.     change_clocksource();
  52.     update_vsyscall(&xtime, clock);
  53. }

大家发现了,update_wall_time是在时钟中断里被调用的,时钟中断每秒运行HZ次,那在粒度为HZ的时钟中断里如何更新纳秒级别的墙上时钟xtime的值呢?

从上述代码里可以看到,在update_wall_time里会从之前注册的clocksource中读取计数值,然后将其换算为纳秒并赋值给xtime!以此为基础,xtime为用户空间提供了精准时钟,比如:
用户空间的gettimeofday函数,进入内核为:
  1. void do_gettimeofday(struct timeval *tv)
  2. {
  3.     struct timespec now;

  4.     getnstimeofday(&now);
  5.     tv->tv_sec = now.tv_sec;
  6.     tv->tv_usec = now.tv_nsec/1000;
  7. }
  8. void getnstimeofday(struct timespec *ts)
  9. {
  10.     cycle_t cycle_now, cycle_delta;
  11.     unsigned long seq;
  12.     s64 nsecs;

  13.     WARN_ON(timekeeping_suspended);

  14.     do {
  15.         seq = read_seqbegin(&xtime_lock);

  16.         *ts = xtime;

  17.         /* read clocksource: */
  18.         cycle_now = clocksource_read(clock);

  19.         //计算自上次中断依赖clock的增加值
  20.         cycle_delta = (cycle_now - clock->cycle_last) & clock->mask;
  21.         //将clock的增加值转化为纳秒值
  22.         nsecs = cyc2ns(clock, cycle_delta);
  23.         nsecs += arch_gettimeoffset();
  24.         //我的设备体系里arch_gettimeoffset=0
  25.     } while (read_seqretry(&xtime_lock, seq));
  26.     timespec_add_ns(ts, nsecs);
  27. }

测试:
  1. #include <sys/time.h>
  2. #include <stdio.h>
  3. int main()
  4. {
  5.   int i=0;
  6.   struct timeval tv;
  7.   for(i=0; i<100; i++){
  8.     gettimeofday(&tv, NULL);
  9.     printf("gettimeofday sec is %d, usec is %d\n", tv.tv_sec, tv.tv_usec);
  10.   }
  11.   return 0;
  12. }
实例一:
在我的1121的内核代码里,仅仅提供jiffies这个clocksource且HZ为200,测试结果如下:
  1. ...
  2. gettimeofday sec is 1196141330, usec is 315000
  3. gettimeofday sec is 1196141330, usec is 315000
  4. gettimeofday sec is 1196141330, usec is 320000
  5. gettimeofday sec is 1196141330, usec is 320000
  6. gettimeofday sec is 1196141330, usec is 320000
  7. gettimeofday sec is 1196141330, usec is 320000
  8. gettimeofday sec is 1196141330, usec is 320000
  9. gettimeofday sec is 1196141330, usec is 320000
  10. gettimeofday sec is 1196141330, usec is 320000
  11. gettimeofday sec is 1196141330, usec is 320000
  12. gettimeofday sec is 1196141330, usec is 320000
  13. gettimeofday sec is 1196141330, usec is 320000
  14. gettimeofday sec is 1196141330, usec is 320000
  15. gettimeofday sec is 1196141330, usec is 320000
  16. gettimeofday sec is 1196141330, usec is 320000
  17. gettimeofday sec is 1196141330, usec is 320000
  18. gettimeofday sec is 1196141330, usec is 320000
  19. gettimeofday sec is 1196141330, usec is 320000
  20. gettimeofday sec is 1196141330, usec is 325000
  21. gettimeofday sec is 1196141330, usec is 325000
  22. ...
可见内核只能提供精度为1/HZ=1/200=5ms=5000us的精度

实例二:在我的1128下,由于注册了它自己的clocksource,且精度比jiffies高(见上面),所以会使用它自己的clocksource,测试结果如下:
  1. gettimeofday sec is 1393836919, usec is 786698
  2. gettimeofday sec is 1393836919, usec is 787353
  3. gettimeofday sec is 1393836919, usec is 787388
  4. gettimeofday sec is 1393836919, usec is 787413
  5. gettimeofday sec is 1393836919, usec is 787435
  6. gettimeofday sec is 1393836919, usec is 787458
  7. gettimeofday sec is 1393836919, usec is 787481
  8. gettimeofday sec is 1393836919, usec is 787504
  9. gettimeofday sec is 1393836919, usec is 787527
  10. gettimeofday sec is 1393836919, usec is 787549
  11. gettimeofday sec is 1393836919, usec is 787572
前后两次调用gettimeofday时最小的差值是22us,远远大于jiffies精度,虽然没有测试到15ns的精度,但可以确认内核使用的是它自己的clocksource!



注:
update_wall_time打印出的信息
offset is cb6f9, interval is a2c2b
offset is a3347, interval is a2c2b
offset is a2e7f, interval is a2c2b
offset is a2e48, interval is a2c2b
offset is a2dcc, interval is a2c2b
offset is a2de4, interval is a2c2b
offset is a2ff9, interval is a2c2b
offset is a2e12, interval is a2c2b
offset is a2de6, interval is a2c2b
offset is a2e63, interval is a2c2b
offset is a2e2b, interval is a2c2b
offset is a2ed5, interval is a2c2b
offset is a2e88, interval is a2c2b
offset is a2e03, interval is a2c2b
offset is a2e66, interval is a2c2b
offset is a2dde, interval is a2c2b
offset is a2e96, interval is a2c2b
offset is a2e06, interval is a2c2b
offset is a2e0d, interval is a2c2b
offset is a2df7, interval is a2c2b
offset is a2e19, interval is a2c2b
offset is a2dd3, interval is a2c2b

上一篇:定时器中断 timer_jiffies这个值的说明
下一篇:linux htimer的实现

文章评论