Given an x86 with a constant TSC, which is useful for measuring real time, how can one convert between the "units" of TSC reference cycles and normal human real-time units like nanoseconds using the TSC calibration factor calculated by Linux at boot-time?
That is, one can certainly calculate the TSC frequency in user-land by taking TSC and clock measurements (e.g., with CLOCK_MONOTONIC) at both ends of some interval to determine the TSC frequency, but Linux has already made this calculation at boot-time since it internally uses the TSC to help out with time-keeping.
For example, you can see the kernel's result with 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
In a worse-case scenario I guess you could try to grep the result out of dmesg at runtime, but that frankly seems terrible, fragile and all sorts of bad0.
The advantages of using the kernel-determined calibration time are many:
cpuid leaf 0x15 so calibration isn't always necessary).gettimeofday and clock_gettime1.It's not all gravy though, some downsides of using Linux's TSC calibration include:
0 For example: systems may not have dmesg installed, you may not be able to run it as a regular user, the accumulated output may have wrapped around so the lines are no longer present, you may get false positives on your grep, the kernel messages are English prose and subject to change, it may be hard to launch a sub-process, etc, etc.
1 It is somewhat debatable whether this matters - but if you are mixing rdtsc calls in with code that also uses OS time-keeping, it may increase precision.
This great answer to the related or duplicated question enumerates two ways to get this information from the kernel: using BPF or using perf.
The following snippet contains the simplest BPF program there is to obtain this value. It gets the value by reading tsc_khz (obtained from /proc/kallsyms) via bpf_probe_read_kernel().
It abuses the fact that the return value of the BPF test run is not checked -- so it omits the creation of a map. The program can be very simply adapted to use a eBPF map if later needed.
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"));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With