Skip to content

Commit fa218f1

Browse files
paulmckrcuKAGA-KOKO
authored andcommitted
clocksource: Limit number of CPUs checked for clock synchronization
Currently, if skew is detected on a clock marked CLOCK_SOURCE_VERIFY_PERCPU, that clock is checked on all CPUs. This is thorough, but might not be what you want on a system with a few tens of CPUs, let alone a few hundred of them. Therefore, by default check only up to eight randomly chosen CPUs. Also provide a new clocksource.verify_n_cpus kernel boot parameter. A value of -1 says to check all of the CPUs, and a non-negative value says to randomly select that number of CPUs, without concern about selecting the same CPU multiple times. However, make use of a cpumask so that a given CPU will be checked at most once. Suggested-by: Thomas Gleixner <[email protected]> # For verify_n_cpus=1. Signed-off-by: Paul E. McKenney <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Acked-by: Feng Tang <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 7560c02 commit fa218f1

File tree

2 files changed

+82
-2
lines changed

2 files changed

+82
-2
lines changed

Documentation/admin-guide/kernel-parameters.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,16 @@
587587
unstable. Defaults to three retries, that is,
588588
four attempts to read the clock under test.
589589

590+
clocksource.verify_n_cpus= [KNL]
591+
Limit the number of CPUs checked for clocksources
592+
marked with CLOCK_SOURCE_VERIFY_PERCPU that
593+
are marked unstable due to excessive skew.
594+
A negative value says to check all CPUs, while
595+
zero says not to check any. Values larger than
596+
nr_cpu_ids are silently truncated to nr_cpu_ids.
597+
The actual CPUs are chosen randomly, with
598+
no replacement if the same CPU is chosen twice.
599+
590600
clearcpuid=BITNUM[,BITNUM...] [X86]
591601
Disable CPUID feature X for the kernel. See
592602
arch/x86/include/asm/cpufeatures.h for the valid bit

kernel/time/clocksource.c

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include <linux/sched.h> /* for spin_unlock_irq() using preempt_count() m68k */
1515
#include <linux/tick.h>
1616
#include <linux/kthread.h>
17+
#include <linux/prandom.h>
18+
#include <linux/cpu.h>
1719

1820
#include "tick-internal.h"
1921
#include "timekeeping_internal.h"
@@ -193,6 +195,8 @@ void clocksource_mark_unstable(struct clocksource *cs)
193195

194196
static ulong max_cswd_read_retries = 3;
195197
module_param(max_cswd_read_retries, ulong, 0644);
198+
static int verify_n_cpus = 8;
199+
module_param(verify_n_cpus, int, 0644);
196200

197201
static bool cs_watchdog_read(struct clocksource *cs, u64 *csnow, u64 *wdnow)
198202
{
@@ -227,6 +231,55 @@ static bool cs_watchdog_read(struct clocksource *cs, u64 *csnow, u64 *wdnow)
227231
static u64 csnow_mid;
228232
static cpumask_t cpus_ahead;
229233
static cpumask_t cpus_behind;
234+
static cpumask_t cpus_chosen;
235+
236+
static void clocksource_verify_choose_cpus(void)
237+
{
238+
int cpu, i, n = verify_n_cpus;
239+
240+
if (n < 0) {
241+
/* Check all of the CPUs. */
242+
cpumask_copy(&cpus_chosen, cpu_online_mask);
243+
cpumask_clear_cpu(smp_processor_id(), &cpus_chosen);
244+
return;
245+
}
246+
247+
/* If no checking desired, or no other CPU to check, leave. */
248+
cpumask_clear(&cpus_chosen);
249+
if (n == 0 || num_online_cpus() <= 1)
250+
return;
251+
252+
/* Make sure to select at least one CPU other than the current CPU. */
253+
cpu = cpumask_next(-1, cpu_online_mask);
254+
if (cpu == smp_processor_id())
255+
cpu = cpumask_next(cpu, cpu_online_mask);
256+
if (WARN_ON_ONCE(cpu >= nr_cpu_ids))
257+
return;
258+
cpumask_set_cpu(cpu, &cpus_chosen);
259+
260+
/* Force a sane value for the boot parameter. */
261+
if (n > nr_cpu_ids)
262+
n = nr_cpu_ids;
263+
264+
/*
265+
* Randomly select the specified number of CPUs. If the same
266+
* CPU is selected multiple times, that CPU is checked only once,
267+
* and no replacement CPU is selected. This gracefully handles
268+
* situations where verify_n_cpus is greater than the number of
269+
* CPUs that are currently online.
270+
*/
271+
for (i = 1; i < n; i++) {
272+
cpu = prandom_u32() % nr_cpu_ids;
273+
cpu = cpumask_next(cpu - 1, cpu_online_mask);
274+
if (cpu >= nr_cpu_ids)
275+
cpu = cpumask_next(-1, cpu_online_mask);
276+
if (!WARN_ON_ONCE(cpu >= nr_cpu_ids))
277+
cpumask_set_cpu(cpu, &cpus_chosen);
278+
}
279+
280+
/* Don't verify ourselves. */
281+
cpumask_clear_cpu(smp_processor_id(), &cpus_chosen);
282+
}
230283

231284
static void clocksource_verify_one_cpu(void *csin)
232285
{
@@ -242,12 +295,22 @@ static void clocksource_verify_percpu(struct clocksource *cs)
242295
int cpu, testcpu;
243296
s64 delta;
244297

298+
if (verify_n_cpus == 0)
299+
return;
245300
cpumask_clear(&cpus_ahead);
246301
cpumask_clear(&cpus_behind);
302+
get_online_cpus();
247303
preempt_disable();
304+
clocksource_verify_choose_cpus();
305+
if (cpumask_weight(&cpus_chosen) == 0) {
306+
preempt_enable();
307+
put_online_cpus();
308+
pr_warn("Not enough CPUs to check clocksource '%s'.\n", cs->name);
309+
return;
310+
}
248311
testcpu = smp_processor_id();
249-
pr_warn("Checking clocksource %s synchronization from CPU %d.\n", cs->name, testcpu);
250-
for_each_online_cpu(cpu) {
312+
pr_warn("Checking clocksource %s synchronization from CPU %d to CPUs %*pbl.\n", cs->name, testcpu, cpumask_pr_args(&cpus_chosen));
313+
for_each_cpu(cpu, &cpus_chosen) {
251314
if (cpu == testcpu)
252315
continue;
253316
csnow_begin = cs->read(cs);
@@ -267,6 +330,7 @@ static void clocksource_verify_percpu(struct clocksource *cs)
267330
cs_nsec_min = cs_nsec;
268331
}
269332
preempt_enable();
333+
put_online_cpus();
270334
if (!cpumask_empty(&cpus_ahead))
271335
pr_warn(" CPUs %*pbl ahead of CPU %d for clocksource %s.\n",
272336
cpumask_pr_args(&cpus_ahead), testcpu, cs->name);
@@ -337,6 +401,12 @@ static void clocksource_watchdog(struct timer_list *unused)
337401
watchdog->name, wdnow, wdlast, watchdog->mask);
338402
pr_warn(" '%s' cs_now: %llx cs_last: %llx mask: %llx\n",
339403
cs->name, csnow, cslast, cs->mask);
404+
if (curr_clocksource == cs)
405+
pr_warn(" '%s' is current clocksource.\n", cs->name);
406+
else if (curr_clocksource)
407+
pr_warn(" '%s' (not '%s') is current clocksource.\n", curr_clocksource->name, cs->name);
408+
else
409+
pr_warn(" No current clocksource.\n");
340410
__clocksource_unstable(cs);
341411
continue;
342412
}

0 commit comments

Comments
 (0)