我有一个在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。但是它确实提供了一种获取
mult
和shift
值的方法,这些值可用于将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/