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

最佳答案

BPF跟踪

以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上进行了测试)

系统点击

如果系统没有可用的bpftrace或gdb而是SystemTap,则可以这样获取它(作为根用户):
# 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;
}

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

10-11 22:49