Detecting CPU Speed

来自osdev
跳到导航 跳到搜索

什么是CPU速度

“CPU速度”有几种不同的定义:

  1. 处理器执行代码的速度(例如每秒指令数)
  2. 处理器的时钟运行速度有多快 (例如每秒周期)

其中CPU执行代码的速度对于确定CPU的性能很重要。 而CPU时钟的运行速度仅在特定情况下有用(例如,校准CPU的TSC以用于测量时间)。

对于这些不同的 “CPU速度” 也有几种不同的测量:

  1. 最佳情况(Best case)
  2. 一般情况(Nominal case)
  3. 平均情况(Average case)
  4. 当前情况(Current case)
  5. 最差情况(Worst case)

例如,看看现代的Intel Core i7 CPU (具有turbo-boost,电源管理和超线程)。 每秒的最佳情况指令出现在以下场景:

  1. 完全没有节流/省电。
  2. 只有一个逻辑CPU在运行 (turbo-boost激活,不使用超线程)
  3. 正在执行在适合CPU“循环缓冲区”的循环中没有依赖项的简单指令
  4. 没有分支错误预测(branch mispredictions)
  5. 没有对内存的访问 (没有数据被传输到/从缓存或RAM传输)

每秒最差情况下的指令正好相反,可能要差几个数量级 (例如每秒40亿条指令的最佳情况和每秒1亿条指令的最差情况)。 每秒标称指令 (通常称为 “每秒标称周期(nominal cycles per second)”) 是对开发人员期望的每秒正常平均指令的估计。 所有这些都是固定值——一个特定的CPU总是有相同的最佳情况、最差情况和标称情况,这些值不会因CPU负载、执行了哪些指令等而改变。

当前(current)每秒指令数是特定时刻每秒的指令数,肯定介于最佳和最差情况之间。 它不能精确地测量,但可以通过在很短的时间内找到每秒的平均指令来估计。 平均情况是需要衡量的。 当前每秒指令数和平均每秒指令数在很大程度上取决于正在运行的代码。 例如,一系列NOP指令的平均每秒指令可能比一系列DIV指令的平均每秒指令高得多。


一般方法

为了判断CPU速度,我们需要两件事:

  1. 能够划分出给定 (精确) 的时间已经过去。
  2. 能够知道一部分代码需要多少“时钟周期”。

一旦解决了这两个子问题,就可以很容易地使用以下命令来判断CPU速度:

prepare_a_timer(X milliseconds ahead);
while (timer has not fired) {
   inc iterations_counter;
}
cpuspeed_mhz = (iteration_counter * clock_cycles_per_iteration)/1000;

请注意,除了非常特殊的情况,使用繁忙循环- busy-loop (甚至经过校准) 来引入延迟是一个坏主意,它应该保持非常小的延迟 (纳米或微秒),当只编程硬件时必须遵守。

还要注意的是,PC模拟器(比如BOCHS)很少是实时的,这导致时钟运行速度似乎比预期的快。

等待给定的时间量

一台PC中有两个电路来处理时间: PIT(Programmable Interval Timer 可编程间隔计时器-英特尔8253和8254是常见的PIT)和RTC(Real Time Clock 实时时钟)。 对于这项任务,PIT可能是两个中更好的一个。

PIT有两种操作模式,可用于告诉CPU速度:

  1. “周期中断(periodic interrupt)”模式(0x36),在该模式下,信号以固定频率发送到中断控制器。 这尤其用到绑定到PC上IRQ0的PIT通道0。
  2. “one shot” 模式 (0x34),其中PIT将以最高速度 (1.19318 MHz) 递减计数器,直到计数器达到零。

应检查通道0是否在0x34模式下触发IRQ。

请注意,从理论上讲,‘’one shot‘’模式可以与‘’轮询‘’方法一起使用,即读取通道数据端口上的当前计数,但是I/O总线周期具有不可预测的延迟,并且需要由程序员来确保时间戳计数器不受此方法的影响。

知道你的循环需要多少个周期

这一步取决于CPU。 在286、386和486上,执行每条指令需要众所周知且确定数量的时钟周期。 这使程序员可以通过查找每条指令的时间,然后对它们进行求和,来准确地说出循环迭代花费了多少个周期。

由于奔腾的多流水线架构(multi-pipelined architecture);这样的数字不再可信(在很大程度上是因为同一条指令可能会根据其周围环境而有不同的计时,这使得计时几乎毫无用处)。

可以创建异常流水线恶意代码,例如:

xor eax,edx
xor edx,eax
xor eax,edx
xor edx,eax
...

一个简单的xor指令需要一个周期,这保证了处理器无法流水线此代码,因为当前指令操作数取决于上次计算的结果。 可以检查一下,对于一个小的计数(从16到64),RDTSC指令将显示指令计数几乎与周期计数完全一致(有时相差1)。 不幸的是,当链变长时,会发生代码缓存未命中,从而破坏整个过程。

例如,在1550个xor链上循环 可能需要一百次迭代,然后它在AMDx86-64上稳定约1575个时钟周期,并且将花费非常长的时间在pentium3上稳定运行。

尽管存在这种不准确的情况,但在给定一个相当准确的计时器的情况下,它在整个处理器代中给出了相对较好的结果。 如果需要非常精确的测量,下一种方法应该更有用。

奔腾开发人员有一个更好的工具来完成计时: “时间戳计数器(Time Stamp Counter)”: 可以使用RDTSC特殊指令读取的内部计数器

rdtscpm1.pdf解释了如何将该功能用于性能监控,并应提供有关如何在奔腾上访问TSC的必要信息。

RDTSC指令访问

时间戳计数器的存在 (从而RDTSC指令的可用性) 可以通过 [CPUID] 指令来检测。 调用eax=1的CPUID将在edx中放置功能标志。 TSC是该字段的第四位。

请注意,在使用CPUID指令之前,请确保处理器通过测试eglags中的 'ID' 位来支持它。 (这是0x200000,仅当支持CPUID指令时才可修改。 对于不支持CPUID的系统,在该位置写入“1”将不起作用。)

在处理器不支持CPUID的情况下,你必须使用更多基于eblags的测试来判断486、386等是否正在运行,然后为该架构选择一个 “校准循环-calibrated loops” (8086通过80486可能有可变的指令时序)。


工作示例代码

上述应用笔记中提供了一个实模式英特尔版权保护的示例。 用户DennisCGC提交的另一个代码片段将给出奔腾处理器的总测量频率。

注意:

  • ‘’irq0_count‘’是一个变量,每次调用计时器中断时,该变量都会增加。
  • 在此代码中,假定 [PIT] 被编程为100Hz (在代码段中提供了计算所需Hz的公式。)
  • 代码假定支持命令CPUID。
;__get_speed__:
   ;首先执行一个cpuid命令,eax=1
   mov  eax,1
   cpuid
   测试edX,字节0x10      ; 测试位 #4。我们有TSC吗?
   jz   detect_end         ; 否?到detect_end去
   ;等待计时器中断被调用。
   mov  ebx, ~[irq0_count]

;__wait_irq0__:

   cmp  ebx, ~[irq0_count]
   jz   wait_irq0
   rdtsc                   ; 读取时间戳计数器
   mov  ~[tscLoDword], eax
   mov  ~[tscHiDword], edx
   add  ebx, 2             ; 设置时间延迟值。
   ; 记住:到目前为止,EBX=~[irq0]-1,所以下一个tick是
   ; 比当前的ebx先两步 ;)

;__wait_for_elapsed_ticks__:

   cmp  ebx, ~[irq0_count] ; 我们遇到延误了吗?
   jnz  wait_for_elapsed_ticks
   rdtsc
   sub eax, ~[tscLoDword]  ; 计算TSC
   sbb edx, ~[tscHiDword]
   ; f(total_ticks_per_Second) =  (1 / total_ticks_per_Second) * 1,000,000
   ; 这会根据MHz进行调整。
   ; 因此,对此:f(100) = (1/100) * 1,000,000 = 10000
   mov ebx, 10000
   div ebx
   ; ax包含以MHz为单位的测量速度
   mov ~[mhz], ax

有关详细信息,请参阅英特尔手册(请参阅链接)。

- 欢迎报告bugs。IM联系方式DennisCGC

不使用中断

本文的语气或风格 可能无法反映整个wiki使用的百科全书式语气。 有关建议,请参阅 Wikipedia's article on tone

我很想说 “可以实”,尽管到目前为止我还没有进行测试,也没有在其他地方听说过。 这里是一些诀窍:

disable()     // 禁用中断(如果仍未完成)
outb(0x43,0x34);   // 将PIT通道0设置为single-shot模式
outb(0x40,0);
outb(0x40,0);      // 编程n个ticks后计数器将为0x10000-n
long stsc=CPU::readTimeStamp();
for (int i=0x1000;i>0;i--);
long etsc=CPU::readTimeStamp();
outb(0x43,0x04);   // 读取PIT计数器命令??
byte lo=inb(0x40);
byte hi=inb(0x40);

现在,我们知道

  • ticks=(0x10000 - (hi*256+lo)) 至少经过了1/1193180秒,并且不超过滴答1。
  • etsc-stsc时钟周期已在同一时间内结束。

因此(etsc-stsc)*1193180 / ticks应该是你的CPU速度(以Hz为单位)...

据我所知,0x1000次迭代在1GHz CPU上导致10个PIT ticks,而在运行BOCHS的同一CPU上导致小于0x8000次ticks。 这当然意味着在非常高速的系统上,发现的速度可能根本不准确,或者更糟糕的是,可能会出现小于1 tick...

这项技术目前正在[Forum:5849]中进行评估。

- 希望你喜欢我的技术/PypeClicker

询问SMBios的CPU速度

正文: SMBIOS

SMBIOS (系统管理BIOS) 规范解决了主板和系统供应商如何通过扩展英特尔架构系统上的BIOS接口以标准格式呈现有关其产品的管理信息。 这些信息旨在使通用仪器能够将这些信息传递给使用DMI、CIM或直接访问的管理应用程序,从而消除了对易出错操作的需求,如探测系统硬件以进行状态检测。

请注意,SMBIOS从未用于初始化目的。 旨在为资产管理系统提供信息,以快速确定哪些计算机包含哪些硬件。 不幸的是,这意味着它可能非常不可靠,尤其是在廉价/家用系统上。 因此,最终这可能不是确定CPU速度的最佳方式。

SMBios处理器信息

处理器信息(类型4)结构描述了SMBios检测到的CPU功能。 标准 的第3.3.5节(第39页)描述了确切的结构。 在该信息中,你将找到处理器类型,系列,制造商等,并且:

  • 外部时钟(总线)频率,即偏移量0x12处的一个字,
  • 最大CPU速度,单位为MHz,是偏移量0x14处的一个字(例如0xe9是233 MHz处理器),
  • 当前CPU速度,以MHz为 (偏移量0x16的字)。

获取SMBIOS结构

SMBios提供了_Get SMBIOS Information_函数,该函数告诉你存在多少结构。 然后,你可以使用 _get SMBIOS Structure_ 函数读取处理器信息。

或者,你可以找到_SMBIOS入口点u,然后手动遍历SMBIOS结构表,查找类型4。

所有这些都在标准(第11页)的“访问SMBIOS信息”结构中进行了描述。

链接

论坛中的相关帖子


其他资源

尤其是第12节: 24161815.pdf 第29页的 “工作频率”

关于SMBIOS