x86/cpu: Avoid cpuinfo-induced IPI pileups
authorPaul E. McKenney <paulmck@kernel.org>
Wed, 2 Sep 2020 20:19:12 +0000 (13:19 -0700)
committerPaul E. McKenney <paulmck@kernel.org>
Sat, 7 Nov 2020 00:58:40 +0000 (16:58 -0800)
The aperfmperf_snapshot_cpu() function is invoked upon access to
/proc/cpuinfo, and it does do an early exit if the specified CPU has
recently done a snapshot.  Unfortunately, the indication that a snapshot
has been completed is set in an IPI handler, and the execution of this
handler can be delayed by any number of unfortunate events.  This means
that a system that starts a number of applications, each of which
parses /proc/cpuinfo, can suffer from an smp_call_function_single()
storm, especially given that each access to /proc/cpuinfo invokes
smp_call_function_single() for all CPUs.  Please note that this is not
theoretical speculation.  Note also that one CPU's pending IPI serves
all requests, so there is no point in ever having more than one IPI
pending to a given CPU.

This commit therefore suppresses duplicate IPIs to a given CPU via a
new ->scfpending field in the aperfmperf_sample structure.  This field
is set to the value one if an IPI is pending to the corresponding CPU
and to zero otherwise.

The aperfmperf_snapshot_cpu() function uses atomic_xchg() to set this
field to the value one and sample the old value.  If this function's
"wait" parameter is zero, smp_call_function_single() is called only if
the old value of the ->scfpending field was zero.  The IPI handler uses
atomic_set_release() to set this new field to zero just before returning,
so that the prior stores into the aperfmperf_sample structure are seen
by future requests that get to the atomic_xchg().  Future requests that
pass the elapsed-time check are ordered by the fact that on x86 loads act
as acquire loads, just as was the case prior to this change.  The return
value is based off of the age of the prior snapshot, just as before.

Reported-by: Dave Jones <davej@codemonkey.org.uk>
[ paulmck: Allow /proc/cpuinfo to take advantage of arch_freq_get_on_cpu(). ]
[ paulmck: Add comment on memory barrier. ]
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Cc: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: <x86@kernel.org>
arch/x86/kernel/cpu/aperfmperf.c

index e2f319d..dd3261d 100644 (file)
@@ -19,6 +19,7 @@
 
 struct aperfmperf_sample {
        unsigned int    khz;
+       atomic_t        scfpending;
        ktime_t time;
        u64     aperf;
        u64     mperf;
@@ -62,17 +63,20 @@ static void aperfmperf_snapshot_khz(void *dummy)
        s->aperf = aperf;
        s->mperf = mperf;
        s->khz = div64_u64((cpu_khz * aperf_delta), mperf_delta);
+       atomic_set_release(&s->scfpending, 0);
 }
 
 static bool aperfmperf_snapshot_cpu(int cpu, ktime_t now, bool wait)
 {
        s64 time_delta = ktime_ms_delta(now, per_cpu(samples.time, cpu));
+       struct aperfmperf_sample *s = per_cpu_ptr(&samples, cpu);
 
        /* Don't bother re-computing within the cache threshold time. */
        if (time_delta < APERFMPERF_CACHE_THRESHOLD_MS)
                return true;
 
-       smp_call_function_single(cpu, aperfmperf_snapshot_khz, NULL, wait);
+       if (!atomic_xchg(&s->scfpending, 1) || wait)
+               smp_call_function_single(cpu, aperfmperf_snapshot_khz, NULL, wait);
 
        /* Return false if the previous iteration was too long ago. */
        return time_delta <= APERFMPERF_STALE_THRESHOLD_MS;
@@ -118,6 +122,8 @@ void arch_freq_prepare_all(void)
 
 unsigned int arch_freq_get_on_cpu(int cpu)
 {
+       struct aperfmperf_sample *s = per_cpu_ptr(&samples, cpu);
+
        if (!cpu_khz)
                return 0;
 
@@ -131,6 +137,8 @@ unsigned int arch_freq_get_on_cpu(int cpu)
                return per_cpu(samples.khz, cpu);
 
        msleep(APERFMPERF_REFRESH_DELAY_MS);
+       atomic_set(&s->scfpending, 1);
+       smp_mb(); /* ->scfpending before smp_call_function_single(). */
        smp_call_function_single(cpu, aperfmperf_snapshot_khz, NULL, 1);
 
        return per_cpu(samples.khz, cpu);