Chinaunix首页 | 论坛 | 博客
  • 博客访问: 98964
  • 博文数量: 28
  • 博客积分: 1435
  • 博客等级: 上尉
  • 技术积分: 265
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-26 11:40
文章分类

全部博文(28)

文章存档

2017年(1)

2012年(1)

2011年(6)

2010年(20)

我的朋友

分类: LINUX

2010-05-07 16:46:42

在Linux内核中提供了几个测量时间的方法,一种是基于jiffies的计时,另一种是直接通过硬件计数器来计时。当然,前者有很强的可移植性,但是精度不够。后者精度很高,但可移植性差,有些CPU根本不提供类似寄存器。所以,当精度能够满足要求的情况下尽可能用前者。本文着重分析后者,前者是一个很大的话题,牵涉到Linux内核的很多方面,下回分解。

以X86体系结构为例:
从pentium开始,很多80x86微处理器都引入TSC,一个用于时间戳计数器的64位的寄存器,它在每个时钟信号 (CLK, CLK是微处理器中一条用于接收外部振荡器的时钟信号输入引线)到来时加一。通过它可以计算CPU的主频,比如:如果微处理器的主频是1MHZ的话,那么TSC就会在1秒内增加1000000。

有专门的指令读取这个寄存器中的值:rdtsc
它把TSC的低32位存放在eax寄存器中,把TSC的高32位存放在edx中。

在Linux内核源码中:(arch/x86/include/asm/msr.h)
下面是定义的一些宏,奇怪的定义,尚且不管,可能它有其他的用途我还没有发现:

50 /*
 51 * both i386 and x86_64 returns 64-bit value in edx:eax, but gcc's "A"
 52 * constraint has different meanings. For i386, "A" means exactly
 53 * edx:eax, while for x86_64 it doesn't mean rdx:rax or edx:eax. Instead,
 54 * it means rax *or* rdx.
 55 */

 56 #ifdef CONFIG_X86_64
 57 #define DECLARE_ARGS(val, low, high) unsigned low, high
 58 #define EAX_EDX_VAL(val, low, high) ((low) | ((u64)(high) << 32))
 59 #define EAX_EDX_ARGS(val, low, high) "a" (low), "d" (high)
 60 #define EAX_EDX_RET(val, low, high) "=a" (low), "=d" (high)
 61 #else
 62 #define DECLARE_ARGS(val, low, high) unsigned long long val
 63 #define EAX_EDX_VAL(val, low, high) (val)
 64 #define EAX_EDX_ARGS(val, low, high) "A" (val)
 65 #define EAX_EDX_RET(val, low, high) "=A" (val)
 66 #endif



227 #define rdtscl(low) \
228 ((low) = (u32)__native_read_tsc())
229
230 #define rdtscll(val) \
231 ((val) = __native_read_tsc())


下面这个函数的作用是:
先声明一个unsigned long long类型的变量val,
然后执行汇编指令rdtsc,且执行结果(edx:eax分别是高32位和低32位)放入val,
最后返回val。

121 static __always_inline unsigned long long __native_read_tsc(void)
122 {
123 DECLARE_ARGS(val, low, high);
124
125 asm volatile("rdtsc" : EAX_EDX_RET(val, low, high));
126
127 return EAX_EDX_VAL(val, low, high);
128 }


以上这些都是在内核中实现的,用户态程序无法直接调用这些实现。但这个特性对于分析算法的执行时间是非常有帮助的。为了使用这个特性,在用户态建立一个文件time.h。
关于cpuid这条指令的叙说如下:
有了rdtsc指令,测试代码的性能就比较简单:在需要测试的代码之前和之后读取并保存time stamp counter,之后两个值比较就可以了解该段代码需要多少个时钟。但是有一点需要注意,由于rdtsc不是serialing指令,意味着rdtsc有 可能和被测试代码一起执行(在time stamp counter被读取出来之前,被测试代码的一部分微操作可能已经完成),所以需要一种机制来阻止rdtsc和被测试代码并行执行。即在rdtsc之间加入一个serialing指令。所谓的serialing指令是指那些直到前面指令的操作完成以后,才会被执行,并清空流水线的指令。多数 serialing指令是特权指令,只有cpuid指令可以在任何特权级别都可以执行,所以使用rdtsc时,一般使用cpuid指令来隔离被测试代码


  1 #ifdef __x86_64__

  2 #define DECLARE_ARGS(val, low, high) unsigned low, high
  3 #define EAX_EDX_VAL(val, low, high) ((low) | ((u64)(high) << 32))
  4 #define EAX_EDX_ARGS(val, low, high) "a" (low), "d" (high)
  5 #define EAX_EDX_RET(val, low, high) "=a" (low), "=d" (high)
  6 #else
  7 #define DECLARE_ARGS(val, low, high) unsigned long long val
  8 #define EAX_EDX_VAL(val, low, high) (val)
  9 #define EAX_EDX_ARGS(val, low, high) "A" (val)
 10 #define EAX_EDX_RET(val, low, high) "=A" (val)
 11 #endif
 12
 13 static inline unsigned long long __rdtscll(void)
 14 {
 15 DECLARE_ARGS(val, low, high);
 16
 17 asm volatile("cpuid; rdtsc" : EAX_EDX_RET(val, low, high));
 18
 19 return EAX_EDX_VAL(val, low, high);
 20 }
 21
 22 #define rdtscll(val) do { (val) = __rdtscll(); } while (0)


在需要测量时间的文件中include "time.h",例如time_test.c:

  1 #include <stdio.h>
  2 #include "time.h"
  3
  4 int main()
  5 {
  6 int i;
  7 unsigned long long t1,t2;
  8 rdtscll(t1);
  9 printf("time after enter main:%lld\n",t1);
 10 for(i=0;i<100;i++);
 11 rdtscll(t2);
 12 printf("time before exit main:%lld\n",t2);
 13 return 0;
 14 }


编译:gcc time_test.c -o time_test
执行:./time_test
结果:time after enter main:958860971460
      time before exit main:958861049517


阅读(1537) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~