linux-kernel - 从 x86 内核获取 TSC 速率

标签 linux-kernel timestamp

我有一个在 Atom 上运行的嵌入式 Linux 系统,这是一个足够新的 CPU,可以有一个不变的 TSC(时间戳计数器),内核在启动时测量其频率。我在自己的代码中使用 TSC 来保持时间(避免内核调用),我的启动代码测量 TSC 速率,但我宁愿只使用内核的测量。有没有办法从内核中检索它?它不在/proc/cpuinfo 的任何地方。

最佳答案

BPFtrace
作为 root,您可以使用 bpftrace 检索内核的 TSC 速率:

# bpftrace -e 'BEGIN { printf("%u\n", *kaddr("tsc_khz")); exit(); }' | tail -n
(在 CentOS 7 和 Fedora 29 上测试过)
这是在 arch/x86/kernel/tsc.c 中定义、导出和维护/校准的值.
广发银行
或者,也可以作为 root,您也可以从 /proc/kcore 读取它,例如:
# gdb /dev/null /proc/kcore -ex 'x/uw 0x'$(grep '\<tsc_khz\>' /proc/kallsyms \
    | cut -d' ' -f1) -batch 2>/dev/null | tail -n 1 | cut -f2
(在 CentOS 7 和 Fedora 29 上测试过)
SystemTap
如果系统没有可用的 bpftrace 或 gdb 但 SystemTap 你可以像这样(以 root 身份)获得它:
# cat tsc_khz.stp 
#!/usr/bin/stap -g

function get_tsc_khz() %{ /* pure */
    THIS->__retvalue = tsc_khz;
%}
probe oneshot {
    printf("%u\n", get_tsc_khz());
}
# ./tsc_khz.stp
当然,你也可以编写一个小的内核模块来提供对 tsc_khz 的访问。通过 /sys伪文件系统。更好的是,有人已经这样做了,还有一个 tsc_freq_khz module is available on GitHub .有了以下应该工作:
# modprobe tsc_freq_khz
$ cat /sys/devices/system/cpu/cpu0/tsc_freq_khz
(在 Fedora 29 上测试,读取 sysfs 文件不需要 root)
内核消息
如果以上都不是一个选项,您可以从内核日志中解析 TSC 速率。但这很快就会变得丑陋,因为您会在不同的硬件和内核上看到不同类型的消息,例如在 Fedora 29 i7 系统上:
$ journalctl --boot | grep 'kernel: tsc:' -i | cut -d' ' -f5-
kernel: tsc: Detected 2800.000 MHz processor
kernel: tsc: Detected 2808.000 MHz TSC
但是在 Fedora 29 Intel Atom 上:
kernel: tsc: Detected 2200.000 MHz processor
在 CentOS 7 i5 系统上:
kernel: tsc: Fast TSC calibration using PIT
kernel: tsc: Detected 1895.542 MHz processor
kernel: tsc: Refined TSC clocksource calibration: 1895.614 MHz
性能值
Linux 内核还没有提供读取 TSC 速率的 API。但它确实提供了一个用于获取 multshift可用于将 TSC 计数转换为纳秒的值。这些值来自 tsc_khz - 也在 arch/x86/kernel/tsc.c - 其中tsc_khz被初始化和校准。它们与用户空间共享。
使用 perf API 并访问共享页面的示例程序:
#include <asm/unistd.h>
#include <inttypes.h>
#include <linux/perf_event.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>

static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
           int cpu, int group_fd, unsigned long flags)
{
    return syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
}
实际代码:
int main(int argc, char **argv)
{
    struct perf_event_attr pe = {
        .type = PERF_TYPE_HARDWARE,
        .size = sizeof(struct perf_event_attr),
        .config = PERF_COUNT_HW_INSTRUCTIONS,
        .disabled = 1,
        .exclude_kernel = 1,
        .exclude_hv = 1
    };
    int fd = perf_event_open(&pe, 0, -1, -1, 0);
    if (fd == -1) {
        perror("perf_event_open failed");
        return 1;
    }
    void *addr = mmap(NULL, 4*1024, PROT_READ, MAP_SHARED, fd, 0);
    if (!addr) {
        perror("mmap failed");
        return 1;
    }
    struct perf_event_mmap_page *pc = addr;
    if (pc->cap_user_time != 1) {
        fprintf(stderr, "Perf system doesn't support user time\n");
        return 1;
    }
    printf("%16s   %5s\n", "mult", "shift");
    printf("%16" PRIu32 "   %5" PRIu16 "\n", pc->time_mult, pc->time_shift);
    close(fd);
}
在 Fedora 29 上进行了测试,它也适用于非 root 用户。
这些值可用于使用如下函数将 TSC 计数转换为纳秒:
static uint64_t mul_u64_u32_shr(uint64_t cyc, uint32_t mult, uint32_t shift)
{
    __uint128_t x = cyc;
    x *= mult;
    x >>= shift;
    return x;
}
CPUID/MSR
获取 TSC 率的另一种方法是 follow DPDK's lead .
x86_64 上的 DPDK 基本上采用以下策略:
  • 通过 cpuid 内部函数读取“时间戳计数器和标称核心 Crystal 时钟信息叶”(不需要特殊权限),如果可用
  • 从 MSR 读取它(需要 rawio capability/dev/cpu/*/msr 的读取权限),如果可能的话
  • 通过其他方式在用户空间校准,否则

  • FWIW,一个快速测试表明 cpuid 叶似乎没有那么广泛可用,例如i7 Skylake 和 goldmont atom 没有。否则,从 DPDK 代码中可以看出,使用 MSR 需要一堆复杂的大小写区分。
    但是,如果程序已经使用 DPDK,那么获取 TSC 速率、获取 TSC 值或转换 TSC 值只是使用正确的 DPDK API 的问题。

    关于linux-kernel - 从 x86 内核获取 TSC 速率,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35123379/

    相关文章:

    Linux内核2.6中sock结构的变化?

    2038-01-19 之后的 MySQL from_unixtime?

    Java 周末计算器 API/库

    c - Linux 内核函数 dm_per_bio_data 有什么作用?

    检查我在linux中使用的串口

    c - Linux内核模式字符串复制

    linux - MS_INVALIDATE 的历史和当前使用

    mysql - TimeStamp 字段和 mysql curdate 之间有什么区别

    python - 将日期字符串转换为 pandas 时间序列索引的最有效方法

    索引字段上的 Postgresql 9.2 时间戳排序不会在一天内按小时部分排序