This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
[PATCH] Get correct CPU frequency from sysfs
- From: Andi Kleen <andi at firstfloor dot org>
- To: libc-alpha at sources dot redhat dot com, drepper at redhat dot com,venkatesh dot pallipadi at intel dot com
- Date: Sun, 2 Aug 2009 21:17:44 +0200
- Subject: [PATCH] Get correct CPU frequency from sysfs
Get correct CPU frequency from sysfs
The frequency of /proc/cpuinfo is the current frequency
which changes all the time based on what the cpufreq
governour decides, but __get_clockfreq() uses it to
measure the RDTSC frequency, is running at a constant
frequency.
This can lead to the clock_gettime cpu time going backwards
and other issues, depending on if the function is lucky
enough to find the CPU running at the highest frequency
or not.
On all recent Intel CPUs the RDTSC frequency is always
the highest p-state (minus Turbo). So get that frequency
from sysfs instead and use it.
I only did this for Intel CPUs, on others it is not
necessarily true.
There's still a small problem that Turbo Boost Mode is presented
in sysfs as highest p-state + max-turbo-bin*1000 (e. g. on my Nehalem
system this gives a 3kHZ error). RDTSC doesn't include Turbo.
There's no easy generic way to distingush this for now and 3kHZ
error doesn't seem to be so bad. This might be something
the kernel needs to fix.
Open issues: this doesn't use libc internal versions of
sscanf etc. so it can be overriden. Is that ok?
The problem is already in the previous code for memmem
etc.
-Andi
2009-08-02 Andi Kleen
* sysdeps/unix/sysv/linux/i386/get_clockfreq.c:
(get_sysfs_frequency, cpuinfo_field): Add.
(__get_clockfreq): Call get_sysfs_frequency.
Signed-off-by: Andi Kleen <ak@linux.intel.com>
diff --git a/sysdeps/unix/sysv/linux/i386/get_clockfreq.c b/sysdeps/unix/sysv/linux/i386/get_clockfreq.c
index 3e2b183..9dc6f48 100644
--- a/sysdeps/unix/sysv/linux/i386/get_clockfreq.c
+++ b/sysdeps/unix/sysv/linux/i386/get_clockfreq.c
@@ -22,7 +22,86 @@
#include <string.h>
#include <unistd.h>
#include <libc-internal.h>
+#include <errno.h>
+#include <stdio.h>
+static int
+cpuinfo_field(char **str, char *name, const char *fmt, void *target)
+{
+ *str = strstr (*str, name);
+ if (*str == NULL)
+ return 0;
+ if (sscanf (*str, fmt, target) != 1)
+ return 0;
+ return 1;
+}
+
+/* This function doesn't really report the true frequency of the CPU
+ (which can change any time), but the frequency RDTSC ticks on. */
+
+static hp_timing_t
+get_sysfs_frequency (char *buf)
+{
+ static const char max_freq_fmt[] =
+ "/sys/devices/system/cpu/cpu%u/cpufreq/cpuinfo_max_freq";
+ char *str = buf;
+ unsigned online_cpu, family, model;
+ char vendor[13];
+ int constant_tsc = 0;
+ char fn[sizeof(max_freq_fmt) + 11];
+ int fd;
+ char data[64];
+ hp_timing_t res = 0;
+ int n;
+
+ /* Determine if RDTSC frequency is reliable */
+
+ if (!cpuinfo_field (&str, "processor", "processor : %u", &online_cpu) ||
+ !cpuinfo_field (&str, "vendor_id", "vendor_id : %12s", vendor) ||
+ !cpuinfo_field (&str, "cpu family", "cpu family : %u", &family) ||
+ !cpuinfo_field (&str, "model", "model : %u", &model))
+ return 0;
+
+ if (!strcmp (vendor, "GenuineIntel"))
+ {
+ /* Same algorithm as kernel uses for constant_tsc. But don't use the
+ feature flags because their semantics may change. */
+ if ((family == 0xf && model >= 3) || (family == 6 && model >= 0x0e))
+ constant_tsc = 1;
+ }
+ /* Add other cases here */
+
+ if (!constant_tsc)
+ return 0;
+
+ snprintf (fn, sizeof fn, max_freq_fmt, online_cpu);
+ fd = open (fn, O_RDONLY);
+ if (fd < 0)
+ {
+ /* To handle hotplug races try the other CPUs before giving up,
+ but bail out early if there is no sysfs or no cpufreq at all. */
+ if (errno == ENOENT && access ("/sys/devices/system/cpu", X_OK) == 0)
+ {
+ /* cpu dir exists, but cpufreq wasn't there? then bail out
+ XXX could be still a race during hotplug. */
+ snprintf (fn, sizeof fn, "/sys/devices/system/cpu/cpu%u",
+ online_cpu);
+ if (access (fn, X_OK))
+ return 0;
+ /* skip to next cpu */
+ return get_sysfs_frequency (str);
+ }
+ }
+
+ if ((n = read (fd, data, (sizeof data) - 1)) > 0 && n != (sizeof data) - 1)
+ {
+ data[n] = 0;
+ sscanf(data, "%llu", &res);
+ res *= 1000;
+ }
+ close(fd);
+ return res;
+}
hp_timing_t
__get_clockfreq (void)
@@ -44,14 +123,25 @@ __get_clockfreq (void)
if (__builtin_expect (fd != -1, 1))
{
/* XXX AFAIK the /proc filesystem can generate "files" only up
- to a size of 4096 bytes. */
- char buf[4096];
+ to a size of 4096 bytes. AK: actually wrong now, but we should
+ find that information in the first 4K hopefully. One more for 0. */
+ char buf[4097];
ssize_t n;
- n = read (fd, buf, sizeof buf);
+ n = read (fd, buf, (sizeof buf) - 1);
+
+ close (fd);
if (__builtin_expect (n, 1) > 0)
{
- char *mhz = memmem (buf, n, "cpu MHz", 7);
+ char *mhz;
+
+ buf[n] = 0;
+
+ result = get_sysfs_frequency (buf);
+ if (result != 0)
+ return result;
+
+ mhz = memmem (buf, n, "cpu MHz", 7);
if (__builtin_expect (mhz != NULL, 1))
{
@@ -83,8 +173,6 @@ __get_clockfreq (void)
result *= 10;
}
}
-
- close (fd);
}
return result;
--
ak@linux.intel.com -- Speaking for myself only.