确定 Linux 上的 TSC 频率

问题描述 投票:0回答:1

给定具有恒定 TSC 的 x86,这对于测量实时非常有用,如何使用 Linux 计算的 TSC 校准因子在 TSC 参考周期的“单位”和正常人类实时单位(如纳秒)之间进行转换在启动时?

也就是说,我们当然可以通过在某个间隔的两端进行 TSC 和时钟测量(例如,使用

CLOCK_MONOTONIC

)来计算用户态的 TSC 频率,以确定 TSC 频率,但 Linux 已经在启动时进行了这一计算-time,因为它内部使用 TSC 来帮助计时。

例如,您可以使用

dmesg | grep tsc

 查看内核的结果: 

[ 0.000000] tsc: PIT calibration matches HPET. 2 loops [ 0.000000] tsc: Detected 3191.922 MHz processor [ 1.733060] tsc: Refined TSC clocksource calibration: 3192.007 MHz

在最糟糕的情况下,我想你可以尝试在运行时从

dmesg

 中 grep 结果,但坦率地说,这看起来很糟糕,脆弱且各种不好
0

使用内核确定的校准时间的优点有很多:

    您不必自己编写 TSC 校准例程,并且您可以非常确定 Linux 校准例程是同类中最好的。
  1. 当使用现有二进制文件出现新内核时,您会自动采用 TSC 校准新技术(例如,最近芯片开始使用
  2. cpuid
     leaf 0x15 宣传其 TSC 频率,因此并不总是需要校准)。
  3. 您不会通过 TSC 校准减慢启动速度。
  4. 您在进程的每次运行中都使用相同的 TSC 值(至少在重新启动之前)。
  5. 您的 TSC 频率在某种程度上与操作系统计时功能(例如
  6. gettimeofday
    clock_gettime
    1)使用的 TSC 频率“一致”。
    
  7. 内核能够在启动时很早就在内核模式下进行 TSC 校准,不受中断和其他进程的影响,并且能够访问底层硬件定时器方向作为其校准源。
但这并不全是肉汁,使用 Linux 的 TSC 校准的一些缺点包括:

    它不适用于所有 Linux 安装(例如,可能不使用 tsc 时钟源的安装)或其他操作系统,因此您可能仍然需要编写后备校准方法。
  1. 有一些理由相信“最近”的校准可能比旧的校准更准确,尤其是在启动后立即进行的校准:晶体行为可能会发生变化,尤其是随着温度的变化,因此您可以通过以下方式获得更准确的频率它手动靠近您将使用它的点。

0 例如:系统可能未安装 dmesg

,您可能无法以普通用户身份运行它,累积的输出可能已回绕,因此线路不再存在,您可能会收到误报你的 grep,内核消息是英文散文,可能会发生变化,可能很难启动子进程等等,等等

1 这是否重要还存在争议 - 但如果您将 rdtsc

 调用与也使用操作系统计时的代码混合,则可能会提高精度。

linux performance x86 rdtsc
1个回答
0
投票
对于相关或重复的问题,这个

很好的答案列举了两种从内核获取此信息的方法:使用 BPF 或使用 perf。

以下代码片段包含获取该值的最简单的 BPF 程序。它通过

tsc_khz

 读取 
/proc/kallsyms
(从 
bpf_probe_read_kernel()
 获得)来获取值。

它滥用了 BPF 测试运行的返回值未被检查的事实——因此它省略了映射的创建。如果以后需要,可以非常简单地对该程序进行修改以使用 eBPF 映射。

const void* resolve_kernel_symbol(const char* name) { FILE* file; char buffer[0x100]; const void* result = NULL; char* saveptr; file = fopen("/proc/kallsyms", "rt"); if (!file) return NULL; while (fgets(buffer, sizeof buffer, file)) { const char* next, *address = strtok_r(buffer, " ", &saveptr); if (!address) continue; if (!strtok_r(NULL, " ", &saveptr)) continue; next = strtok_r(NULL, "\n", &saveptr); if (!next) continue; if (!strcmp(name, next)) { result = (const void*)strtoul(address, &saveptr, 0x10); goto end; } } end: fclose(file); return result; } int read_kernel_int(const void* address) { int prog_fd, result; const struct bpf_insn program[] = { /* r10 = stack */ /* Load the first argument (the destination address, local variable). */ BPF_MOV64_REG(BPF_REG_1, BPF_REG_10), /* r1 = stk */ BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, -4), /* r1 -= 4 */ /* Load the second argument, 4 (sizeof int). */ BPF_MOV64_IMM(BPF_REG_2, 4), /* r2 = 4 */ /* Load the third argument, address of symbol. */ BPF_LD_IMM64(BPF_REG_3, (long)address), /* Call bpf_probe_read_kernel. */ BPF_JMP_IMM(BPF_CALL, 0, (long)bpf_probe_read_kernel, 0), /* Set the exit code to the read value. */ /* r1 is caller-saved, so cannot be used. */ BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_10, -4), /* r0 = *((u32*)stk-1) */ BPF_EXIT_INSN() }; { union bpf_attr attr = {0}; attr.prog_type = BPF_PROG_TYPE_RAW_TRACEPOINT; attr.insns = (__u64)program; attr.insn_cnt = sizeof program/sizeof program[0]; attr.license = (__u64)"GPL"; if ((prog_fd = syscall(SYS_bpf, BPF_PROG_LOAD, &attr, sizeof attr)) < 0) return -1; } { union bpf_attr attr = {0}; attr.test.prog_fd = prog_fd; if (syscall(SYS_bpf, BPF_PROG_TEST_RUN, &attr, sizeof attr) < 0) { result = -1; goto out; } result = attr.test.retval; } out: close(prog_fd); return result; } int get_tsc_freq(void) { return read_kernel_int(resolve_kernel_symbol("tsc_khz")); }
    
© www.soinside.com 2019 - 2024. All rights reserved.