This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[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.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]