mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/4] Stop the idle tick on polling idle on nohz_full CPUs
@ 2026-11-11 11:43 Marco Crivellari
  2026-11-11 11:43 ` [PATCH v2 1/4] tick/nohz: Re-evaluate the tick on return to user space Marco Crivellari
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Marco Crivellari @ 2026-11-11 11:43 UTC (permalink / raw)
  To: linux-kernel
  Cc: Frederic Weisbecker, Ingo Molnar, Marco Crivellari, Michal Hocko,
	Aaron Tomlin, Marcelo Tosatti, Anna-Maria Behnsen, Ben Segall,
	Dietmar Eggemann, Jonathan Corbet, Juri Lelli, K Prateek Nayak,
	Mel Gorman, Paul E. McKenney, Peter Zijlstra, Randy Dunlap,
	Shuah Khan, Steven Rostedt, Thomas Gleixner, Valentin Schneider,
	Vincent Guittot

Hi,

On nohz_full CPUs booted with idle=poll, the tick interrupts the task once
after every wakeup from idle. do_idle() restarts the tick before polling,
and on wakeup tick_nohz_idle_exit() only re-evaluates the tick if it is
stopped, so it keeps running and fires in userspace before
tick_nohz_irq_exit() stops it. Without idle=poll this doesn't happen:
since commit a5183862e76f ("tick/nohz: Conditionally restart tick on idle
exit") a nohz_full CPU leaves idle with the tick still stopped.

Marcelo Tosatti proposed stopping the tick on polling idle entry:
  https://lore.kernel.org/all/aQiWfnnSzxsnwa2o@tpad/

Following Frederic's review, this series does it only on nohz_full CPUs,
and handles on the target CPU the cases where stopping the tick in polling
idle would leave a timer unprogrammed.

[ P1 ] tick/nohz: Re-evaluate the tick on return to user space
  On nohz_full CPUs the tick is re-evaluated on IRQ exit and on idle exit.

  This is not enough on return to user space. If the tick is left running in
  the kernel, for example because a tick dependency was cleared
  (tick_nohz_dep_clear*() don't kick the CPU) or a timer that kept it
  running was cancelled, it fires at least once in userspace before
  tick_nohz_irq_exit() stops it.

  Stop it from context tracking right before resuming userspace. When the
  tick is already stopped, the common case, this costs a few flag tests on
  each return to userspace.

[ P2 ] Don't handle polling idle as dynticks idle

  TS_FLAG_INIDLE tells nohz whether the tick is managed by the idle loop or
  on IRQ exit. With idle=poll it is set while polling, but cpu_idle_poll()
  doesn't run the idle loop again on interrupts: a nohz_full kick or the
  expiry of a stopped tick isn't re-evaluated until something sets
  TIF_NEED_RESCHED. The tick is therefore restarted before polling, and on
  nohz_full CPUs it then keeps running after the wake-up and interrupts
  userspace.

  Enter dynticks idle only on the cpuidle path, from cpuidle_idle_call(),
  and leave it before cpu_idle_poll(). While polling, the tick is then
  managed on IRQ exit as for a running task: on nohz_full CPUs it stays
  stopped if possible, and nothing is left to do on wake-up, so the task
  doesn't pay for a tick stop after waking up. On other CPUs, leaving
  dynticks idle restarts the tick as before.

[ P3 ] tick/nohz: Remove tick_nohz_idle_restart_tick()

  Polling idle now leaves dynticks idle through tick_nohz_idle_exit()
  before cpu_idle_poll(), which restarts the tick where needed.
  tick_nohz_idle_restart_tick() has no users left: remove it.

[ P4 ] Documentation: no_hz: Add tick stop in polling idle

  Document the above in the idle=poll item of "Known Issues".

With big effort on the Claude side, I made a test suite to help testing
the work:

  https://github.com/DispatchCode/nohz-full-polling-idle-tick-test-suite

The following is a short summary of the results.

On x86_64 guest, HZ=1000, nohz_full=2-3 idle=poll), a task alternating
100us sleeps with 10ms of userspace work, 200 times:
  - ticks in userspace:              200/200 -> 0/200
  - ticks while polling:             35 -> 0
  - housekeeping CPUs:               unchanged (tick runs while polling)
  - P2 without the kick change:      15 of 20 timers enqueued from an
                                     interrupt on the polling CPU lost
  - P2 without the stopped tick fix: the second of two queued timers up
                                     to 1.9s late
Tick dependencies (POSIX CPU timers, two tasks on the CPU) and CPU hotplug
were also checked.

Thanks,

  Marco

---
Changes in v2:
 - P1: The only change part of this patch is re-evaluate the tick on
   return to user space.
 
 - P2: Changed as suggested by Frederic: TS_FLAG_INIDLE is only set when
   the CPU goes idle and not in polling idle; tick_nohz_idle_enter() is
   called on every idle loop and runs with interrupt disabled. The tick
   is not restarted before polling idle, so
   tick_nohz_idle_restart_tick() has been removed.

 - P3: Remove tick_nohz_idle_restart_tick(), now unused.

Marco Crivellari (4):
  tick/nohz: Re-evaluate the tick on return to user space
  sched/idle: Don't handle polling idle as dynticks idle
  tick/nohz: Remove tick_nohz_idle_restart_tick()
  Documentation: no_hz: Add tick stop in polling idle

 Documentation/timers/no_hz.rst |  2 +
 include/linux/tick.h           | 10 ++++-
 kernel/context_tracking.c      |  8 ++++
 kernel/sched/idle.c            | 13 ++++++-
 kernel/time/tick-sched.c       | 68 ++++++++++++++++++++--------------
 5 files changed, 69 insertions(+), 32 deletions(-)

-- 
2.56.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 1/4] tick/nohz: Re-evaluate the tick on return to user space
  2026-11-11 11:43 [PATCH v2 0/4] Stop the idle tick on polling idle on nohz_full CPUs Marco Crivellari
@ 2026-11-11 11:43 ` Marco Crivellari
  2026-11-11 16:25   ` Frederic Weisbecker
  2026-11-11 11:43 ` [PATCH v2 2/4] sched/idle: Don't handle polling idle as dynticks idle Marco Crivellari
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 10+ messages in thread
From: Marco Crivellari @ 2026-11-11 11:43 UTC (permalink / raw)
  To: linux-kernel
  Cc: Frederic Weisbecker, Ingo Molnar, Marco Crivellari, Michal Hocko,
	Aaron Tomlin, Marcelo Tosatti, Paul E. McKenney,
	Anna-Maria Behnsen, Thomas Gleixner

On nohz_full CPUs the tick is re-evaluated on IRQ exit and on idle exit.

This is not enough on return to user space. If the tick is left running in
the kernel, for example because a tick dependency was cleared
(tick_nohz_dep_clear*() don't kick the CPU) or a timer that kept it
running was cancelled, it fires at least once in userspace before
tick_nohz_irq_exit() stops it.

Stop it from context tracking right before resuming userspace. When the
tick is already stopped, the common case, this costs a few flag tests on
each return to userspace.

Cc: Paul E. McKenney <paulmck@kernel.org>
Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
Cc: Thomas Gleixner <tglx@kernel.org>
Suggested-by: Frederic Weisbecker <frederic@kernel.org>
Signed-off-by: Marco Crivellari <marco.crivellari@suse.com>
---
 include/linux/tick.h      |  8 ++++++++
 kernel/context_tracking.c |  8 ++++++++
 kernel/time/tick-sched.c  | 24 ++++++++++++++++++++++++
 3 files changed, 40 insertions(+)

diff --git a/include/linux/tick.h b/include/linux/tick.h
index b121c5d53e30..8bfbb053f4fd 100644
--- a/include/linux/tick.h
+++ b/include/linux/tick.h
@@ -269,6 +269,7 @@ static inline void tick_dep_clear_signal(struct signal_struct *signal,
 extern void tick_nohz_full_kick_cpu(int cpu);
 extern void __tick_nohz_task_switch(void);
 extern void __init tick_nohz_full_setup(cpumask_var_t cpumask);
+extern void __tick_nohz_user_enter(void);
 #else
 static inline bool tick_nohz_full_enabled(void) { return false; }
 static inline bool tick_nohz_full_cpu(int cpu) { return false; }
@@ -294,6 +295,7 @@ static inline void tick_dep_clear_signal(struct signal_struct *signal,
 static inline void tick_nohz_full_kick_cpu(int cpu) { }
 static inline void __tick_nohz_task_switch(void) { }
 static inline void tick_nohz_full_setup(cpumask_var_t cpumask) { }
+static inline void __tick_nohz_user_enter(void) { }
 #endif
 
 static inline void tick_nohz_task_switch(void)
@@ -302,6 +304,12 @@ static inline void tick_nohz_task_switch(void)
 		__tick_nohz_task_switch();
 }
 
+static inline void tick_nohz_user_enter(void)
+{
+	if (tick_nohz_full_enabled())
+		__tick_nohz_user_enter();
+}
+
 static inline void tick_nohz_user_enter_prepare(void)
 {
 	if (tick_nohz_full_cpu(smp_processor_id()))
diff --git a/kernel/context_tracking.c b/kernel/context_tracking.c
index a743e7ffa6c0..347f99e1880f 100644
--- a/kernel/context_tracking.c
+++ b/kernel/context_tracking.c
@@ -24,6 +24,7 @@
 #include <linux/export.h>
 #include <linux/kprobes.h>
 #include <trace/events/rcu.h>
+#include <linux/tick.h>
 
 
 DEFINE_PER_CPU(struct context_tracking, context_tracking) = {
@@ -466,6 +467,13 @@ void noinstr __ct_user_enter(enum ctx_state state)
 			 */
 			if (state == CT_STATE_USER) {
 				instrumentation_begin();
+				/*
+				 * On nohz_full CPUs the tick may have been left running in the
+				 * kernel, e.g. kept by a tick dependency that is now gone.
+				 * Stop it now rather than let it interrupt userspace once
+				 * more before the next IRQ exit notices.
+				 */
+				tick_nohz_user_enter();
 				trace_user_enter(0);
 				vtime_user_enter(current);
 				instrumentation_end();
diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c
index c6d86e4da8a9..645dba919386 100644
--- a/kernel/time/tick-sched.c
+++ b/kernel/time/tick-sched.c
@@ -1071,6 +1071,30 @@ static void tick_nohz_full_update_tick(struct tick_sched *ts)
 	__tick_nohz_full_update_tick(ts, ktime_get());
 }
 
+#ifdef CONFIG_NO_HZ_FULL
+void __tick_nohz_user_enter(void)
+{
+	struct tick_sched *ts;
+	int cpu;
+
+	lockdep_assert_irqs_disabled();
+
+	cpu = smp_processor_id();
+	if (!tick_nohz_full_cpu(cpu))
+		return;
+
+	ts = this_cpu_ptr(&tick_cpu_sched);
+	if (!tick_sched_flag_test(ts, TS_FLAG_NOHZ))
+		return;
+
+	if (tick_sched_flag_test(ts, TS_FLAG_STOPPED))
+		return;
+
+	if (can_stop_full_tick(cpu, ts))
+		tick_nohz_full_stop_tick(ts, cpu);
+}
+#endif
+
 /*
  * A pending softirq outside an IRQ (or softirq disabled section) context
  * should be waiting for ksoftirqd to handle it. Therefore we shouldn't
-- 
2.56.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 2/4] sched/idle: Don't handle polling idle as dynticks idle
  2026-11-11 11:43 [PATCH v2 0/4] Stop the idle tick on polling idle on nohz_full CPUs Marco Crivellari
  2026-11-11 11:43 ` [PATCH v2 1/4] tick/nohz: Re-evaluate the tick on return to user space Marco Crivellari
@ 2026-11-11 11:43 ` Marco Crivellari
  2026-11-11 16:57   ` Frederic Weisbecker
  2026-11-11 11:43 ` [PATCH v2 3/4] tick/nohz: Remove tick_nohz_idle_restart_tick() Marco Crivellari
  2026-11-11 11:43 ` [PATCH v2 4/4] Documentation: no_hz: Add tick stop in polling idle Marco Crivellari
  3 siblings, 1 reply; 10+ messages in thread
From: Marco Crivellari @ 2026-11-11 11:43 UTC (permalink / raw)
  To: linux-kernel
  Cc: Frederic Weisbecker, Ingo Molnar, Marco Crivellari, Michal Hocko,
	Aaron Tomlin, Marcelo Tosatti, Peter Zijlstra, Juri Lelli,
	Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
	Mel Gorman, Valentin Schneider, K Prateek Nayak,
	Paul E. McKenney, Anna-Maria Behnsen, Thomas Gleixner

TS_FLAG_INIDLE tells nohz whether the tick is managed by the idle loop or
on IRQ exit. With idle=poll it is set while polling, but cpu_idle_poll()
doesn't run the idle loop again on interrupts: a nohz_full kick or the
expiry of a stopped tick isn't re-evaluated until something sets
TIF_NEED_RESCHED. The tick is therefore restarted before polling, and on
nohz_full CPUs it then keeps running after the wake-up and interrupts
userspace.

Enter dynticks idle only on the cpuidle path, from cpuidle_idle_call(),
and leave it before cpu_idle_poll(). While polling, the tick is then
managed on IRQ exit as for a running task: on nohz_full CPUs it stays
stopped if possible, and nothing is left to do on wake-up, so the task
doesn't pay for a tick stop after waking up. On other CPUs, leaving
dynticks idle restarts the tick as before.

tick_nohz_idle_enter() may now be called on each idle loop iteration and
always runs with interrupts disabled; tick_nohz_idle_exit() may be called
outside dynticks idle and with interrupts disabled.

Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Juri Lelli <juri.lelli@redhat.com>
Cc: Vincent Guittot <vincent.guittot@linaro.org>
Cc: Dietmar Eggemann <dietmar.eggemann@arm.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Ben Segall <bsegall@google.com>
Cc: Mel Gorman <mgorman@suse.de>
Cc: Valentin Schneider <vschneid@redhat.com>
Cc: K Prateek Nayak <kprateek.nayak@amd.com>
Cc: Paul E. McKenney <paulmck@kernel.org>
Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
Cc: Thomas Gleixner <tglx@kernel.org>
Link: https://lore.kernel.org/all/aQiWfnnSzxsnwa2o@tpad/
Suggested-by: Frederic Weisbecker <frederic@kernel.org>
Signed-off-by: Marco Crivellari <marco.crivellari@suse.com>
---
 kernel/sched/idle.c      | 13 +++++++++++--
 kernel/time/tick-sched.c | 27 ++++++++++++++++-----------
 2 files changed, 27 insertions(+), 13 deletions(-)

diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index e0c10a5dbda2..e0e3c06da528 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -193,6 +193,8 @@ static void cpuidle_idle_call(bool stop_tick)
 		return;
 	}
 
+	tick_nohz_idle_enter();
+
 	if (cpuidle_not_available(drv, dev)) {
 		idle_call_stop_or_retain_tick(stop_tick);
 
@@ -303,7 +305,6 @@ static void do_idle(void)
 	 */
 
 	__current_set_polling();
-	tick_nohz_idle_enter();
 
 	while (!need_resched()) {
 
@@ -349,7 +350,15 @@ static void do_idle(void)
 		 * idle as we know that the IPI is going to arrive right away.
 		 */
 		if (cpu_idle_force_poll || tick_check_broadcast_expired()) {
-			tick_nohz_idle_restart_tick();
+			/*
+			 * Polling idle is not dynticks idle: leave it before
+			 * polling so that the tick is managed on IRQ exit as for
+			 * a running task. On nohz_full CPUs it stays stopped and
+			 * nothing is left to do on wake-up that would delay the
+			 * task. On other CPUs, leaving dynticks idle restarts it
+			 * as before.
+			 */
+			tick_nohz_idle_exit();
 			cpu_idle_poll();
 		} else {
 			cpuidle_idle_call(got_tick);
diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c
index 645dba919386..0b798dc39511 100644
--- a/kernel/time/tick-sched.c
+++ b/kernel/time/tick-sched.c
@@ -1227,23 +1227,22 @@ static void tick_nohz_clock_wakeup(struct tick_sched *ts)
 /**
  * tick_nohz_idle_enter - prepare for entering idle on the current CPU
  *
- * Called when we start the idle loop.
+ * Called with IRQs disabled on each idle loop iteration that enters
+ * cpuidle. Only the first call enters dynticks idle.
  */
 void tick_nohz_idle_enter(void)
 {
-	struct tick_sched *ts;
+	struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
 
-	lockdep_assert_irqs_enabled();
+	lockdep_assert_irqs_disabled();
 
-	local_irq_disable();
+	if (tick_sched_flag_test(ts, TS_FLAG_INIDLE))
+		return;
 
-	ts = this_cpu_ptr(&tick_cpu_sched);
 	WARN_ON_ONCE(ts->timer_expires_base);
 	tick_sched_flag_set(ts, TS_FLAG_INIDLE);
 	ts->idle_entrytime = ktime_get();
 	tick_nohz_clock_sleep(ts);
-
-	local_irq_enable();
 }
 
 /**
@@ -1415,15 +1414,21 @@ static void tick_nohz_idle_update_tick(struct tick_sched *ts, ktime_t now)
  *        as long as possible.
  *   2.2) If the tick has dependencies, restart the tick.
  *
+ * Also called before polling idle, which is not dynticks idle: it may
+ * therefore be called outside dynticks idle, with or without IRQs
+ * disabled.
  */
 void tick_nohz_idle_exit(void)
 {
 	struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
+	unsigned long flags;
 	ktime_t now;
 
-	local_irq_disable();
+	local_irq_save(flags);
+
+	if (!tick_sched_flag_test(ts, TS_FLAG_INIDLE))
+		goto out;
 
-	WARN_ON_ONCE(!tick_sched_flag_test(ts, TS_FLAG_INIDLE));
 	WARN_ON_ONCE(ts->timer_expires_base);
 
 	tick_sched_flag_clear(ts, TS_FLAG_INIDLE);
@@ -1434,8 +1439,8 @@ void tick_nohz_idle_exit(void)
 		tick_nohz_dyntick_stop(ts, now);
 		tick_nohz_idle_update_tick(ts, now);
 	}
-
-	local_irq_enable();
+out:
+	local_irq_restore(flags);
 }
 
 /*
-- 
2.56.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 3/4] tick/nohz: Remove tick_nohz_idle_restart_tick()
  2026-11-11 11:43 [PATCH v2 0/4] Stop the idle tick on polling idle on nohz_full CPUs Marco Crivellari
  2026-11-11 11:43 ` [PATCH v2 1/4] tick/nohz: Re-evaluate the tick on return to user space Marco Crivellari
  2026-11-11 11:43 ` [PATCH v2 2/4] sched/idle: Don't handle polling idle as dynticks idle Marco Crivellari
@ 2026-11-11 11:43 ` Marco Crivellari
  2026-11-11 16:59   ` Frederic Weisbecker
  2026-11-11 11:43 ` [PATCH v2 4/4] Documentation: no_hz: Add tick stop in polling idle Marco Crivellari
  3 siblings, 1 reply; 10+ messages in thread
From: Marco Crivellari @ 2026-11-11 11:43 UTC (permalink / raw)
  To: linux-kernel
  Cc: Frederic Weisbecker, Ingo Molnar, Marco Crivellari, Michal Hocko,
	Aaron Tomlin, Marcelo Tosatti, Anna-Maria Behnsen,
	Thomas Gleixner

Polling idle now leaves dynticks idle through tick_nohz_idle_exit()
before cpu_idle_poll(), which restarts the tick where needed.
tick_nohz_idle_restart_tick() has no users left: remove it.

Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
Cc: Thomas Gleixner <tglx@kernel.org>
Suggested-by: Frederic Weisbecker <frederic@kernel.org>
Signed-off-by: Marco Crivellari <marco.crivellari@suse.com>
---
 include/linux/tick.h     |  2 --
 kernel/time/tick-sched.c | 17 -----------------
 2 files changed, 19 deletions(-)

diff --git a/include/linux/tick.h b/include/linux/tick.h
index 8bfbb053f4fd..686f7e58738b 100644
--- a/include/linux/tick.h
+++ b/include/linux/tick.h
@@ -130,7 +130,6 @@ extern bool tick_nohz_tick_stopped(void);
 extern bool tick_nohz_tick_stopped_cpu(int cpu);
 extern void tick_nohz_idle_stop_tick(void);
 extern void tick_nohz_idle_retain_tick(void);
-extern void tick_nohz_idle_restart_tick(void);
 extern void tick_nohz_idle_enter(void);
 extern void tick_nohz_idle_exit(void);
 extern void tick_nohz_irq_exit(void);
@@ -145,7 +144,6 @@ static inline int tick_nohz_tick_stopped(void) { return 0; }
 static inline int tick_nohz_tick_stopped_cpu(int cpu) { return 0; }
 static inline void tick_nohz_idle_stop_tick(void) { }
 static inline void tick_nohz_idle_retain_tick(void) { }
-static inline void tick_nohz_idle_restart_tick(void) { }
 static inline void tick_nohz_idle_enter(void) { }
 static inline void tick_nohz_idle_exit(void) { }
 static inline bool tick_nohz_idle_got_tick(void) { return false; }
diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c
index 0b798dc39511..ed4a02881653 100644
--- a/kernel/time/tick-sched.c
+++ b/kernel/time/tick-sched.c
@@ -1374,23 +1374,6 @@ static void tick_nohz_dyntick_stop(struct tick_sched *ts, ktime_t now)
 	kcpustat_dyntick_stop(now, ts->last_tick - TICK_NSEC);
 }
 
-void tick_nohz_idle_restart_tick(void)
-{
-	struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
-
-	if (tick_sched_flag_test(ts, TS_FLAG_STOPPED)) {
-		/*
-		 * Update entrytime here in case the tick restart is due to temporary
-		 * polling on forced broadcast. The tick may be stopped again later within
-		 * the same idle trip. The idle_entrytime was updated recently but make sure
-		 * no tiny amount of idle time is accounted twice.
-		 */
-		ts->idle_entrytime = ktime_get();
-		tick_nohz_dyntick_stop(ts, ts->idle_entrytime);
-		tick_nohz_restart_sched_tick(ts, ts->idle_entrytime);
-	}
-}
-
 static void tick_nohz_idle_update_tick(struct tick_sched *ts, ktime_t now)
 {
 	if (tick_nohz_full_cpu(smp_processor_id()))
-- 
2.56.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 4/4] Documentation: no_hz: Add tick stop in polling idle
  2026-11-11 11:43 [PATCH v2 0/4] Stop the idle tick on polling idle on nohz_full CPUs Marco Crivellari
                   ` (2 preceding siblings ...)
  2026-11-11 11:43 ` [PATCH v2 3/4] tick/nohz: Remove tick_nohz_idle_restart_tick() Marco Crivellari
@ 2026-11-11 11:43 ` Marco Crivellari
  2026-11-11 17:00   ` Frederic Weisbecker
  3 siblings, 1 reply; 10+ messages in thread
From: Marco Crivellari @ 2026-11-11 11:43 UTC (permalink / raw)
  To: linux-kernel
  Cc: Frederic Weisbecker, Ingo Molnar, Marco Crivellari, Michal Hocko,
	Aaron Tomlin, Marcelo Tosatti, Anna-Maria Behnsen,
	Thomas Gleixner, Jonathan Corbet, Shuah Khan, Randy Dunlap

On nohz_full CPUs the scheduling-clock tick is stopped while polling.
Update the documentation accordingly.

Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
Cc: Thomas Gleixner <tglx@kernel.org>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Shuah Khan <skhan@linuxfoundation.org>
Cc: Randy Dunlap <rdunlap@infradead.org>
Signed-off-by: Marco Crivellari <marco.crivellari@suse.com>
---
 Documentation/timers/no_hz.rst | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/timers/no_hz.rst b/Documentation/timers/no_hz.rst
index 4006cab64079..86cea04d2a49 100644
--- a/Documentation/timers/no_hz.rst
+++ b/Documentation/timers/no_hz.rst
@@ -246,6 +246,8 @@ Known Issues
 		be even worse than that of dyntick-idle.  Furthermore,
 		this parameter effectively disables Turbo Mode on Intel
 		CPUs, which can significantly reduce maximum performance.
+		Note also that on nohz_full CPUs the scheduling-clock tick
+		is stopped while polling, as in the other idle states.
 
 *	Adaptive-ticks slows user/kernel transitions slightly.
 	This is not expected to be a problem for computationally intensive
-- 
2.56.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 1/4] tick/nohz: Re-evaluate the tick on return to user space
  2026-11-11 11:43 ` [PATCH v2 1/4] tick/nohz: Re-evaluate the tick on return to user space Marco Crivellari
@ 2026-11-11 16:25   ` Frederic Weisbecker
  0 siblings, 0 replies; 10+ messages in thread
From: Frederic Weisbecker @ 2026-11-11 16:25 UTC (permalink / raw)
  To: Marco Crivellari
  Cc: linux-kernel, Ingo Molnar, Michal Hocko, Aaron Tomlin,
	Marcelo Tosatti, Paul E. McKenney, Anna-Maria Behnsen,
	Thomas Gleixner

Le Wed, Nov 11, 2026 at 12:43:47PM +0100, Marco Crivellari a écrit :
> On nohz_full CPUs the tick is re-evaluated on IRQ exit and on idle exit.
> 
> This is not enough on return to user space. If the tick is left running in
> the kernel, for example because a tick dependency was cleared
> (tick_nohz_dep_clear*() don't kick the CPU) or a timer that kept it
> running was cancelled, it fires at least once in userspace before
> tick_nohz_irq_exit() stops it.
> 
> Stop it from context tracking right before resuming userspace. When the
> tick is already stopped, the common case, this costs a few flag tests on
> each return to userspace.
> 
> Cc: Paul E. McKenney <paulmck@kernel.org>
> Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
> Cc: Thomas Gleixner <tglx@kernel.org>
> Suggested-by: Frederic Weisbecker <frederic@kernel.org>
> Signed-off-by: Marco Crivellari <marco.crivellari@suse.com>
> ---
>  include/linux/tick.h      |  8 ++++++++
>  kernel/context_tracking.c |  8 ++++++++
>  kernel/time/tick-sched.c  | 24 ++++++++++++++++++++++++
>  3 files changed, 40 insertions(+)
> 
> diff --git a/include/linux/tick.h b/include/linux/tick.h
> index b121c5d53e30..8bfbb053f4fd 100644
> --- a/include/linux/tick.h
> +++ b/include/linux/tick.h
> @@ -269,6 +269,7 @@ static inline void tick_dep_clear_signal(struct signal_struct *signal,
>  extern void tick_nohz_full_kick_cpu(int cpu);
>  extern void __tick_nohz_task_switch(void);
>  extern void __init tick_nohz_full_setup(cpumask_var_t cpumask);
> +extern void __tick_nohz_user_enter(void);
>  #else
>  static inline bool tick_nohz_full_enabled(void) { return false; }
>  static inline bool tick_nohz_full_cpu(int cpu) { return false; }
> @@ -294,6 +295,7 @@ static inline void tick_dep_clear_signal(struct signal_struct *signal,
>  static inline void tick_nohz_full_kick_cpu(int cpu) { }
>  static inline void __tick_nohz_task_switch(void) { }
>  static inline void tick_nohz_full_setup(cpumask_var_t cpumask) { }
> +static inline void __tick_nohz_user_enter(void) { }
>  #endif
>  
>  static inline void tick_nohz_task_switch(void)
> @@ -302,6 +304,12 @@ static inline void tick_nohz_task_switch(void)
>  		__tick_nohz_task_switch();
>  }
>  
> +static inline void tick_nohz_user_enter(void)
> +{
> +	if (tick_nohz_full_enabled())
> +		__tick_nohz_user_enter();
> +}
> +
>  static inline void tick_nohz_user_enter_prepare(void)
>  {
>  	if (tick_nohz_full_cpu(smp_processor_id()))
> diff --git a/kernel/context_tracking.c b/kernel/context_tracking.c
> index a743e7ffa6c0..347f99e1880f 100644
> --- a/kernel/context_tracking.c
> +++ b/kernel/context_tracking.c
> @@ -24,6 +24,7 @@
>  #include <linux/export.h>
>  #include <linux/kprobes.h>
>  #include <trace/events/rcu.h>
> +#include <linux/tick.h>
>  
>  
>  DEFINE_PER_CPU(struct context_tracking, context_tracking) = {
> @@ -466,6 +467,13 @@ void noinstr __ct_user_enter(enum ctx_state state)
>  			 */
>  			if (state == CT_STATE_USER) {
>  				instrumentation_begin();
> +				/*
> +				 * On nohz_full CPUs the tick may have been left running in the
> +				 * kernel, e.g. kept by a tick dependency that is now gone.
> +				 * Stop it now rather than let it interrupt userspace once
> +				 * more before the next IRQ exit notices.
> +				 */
> +				tick_nohz_user_enter();

You can move that a little bit further, outside "state == CT_STATE_USER", right
before rcu_irq_work_resched(). This will need a new pair of
instrumentation_begin()/end().

This way it also applies before switching to guest.


>  				trace_user_enter(0);
>  				vtime_user_enter(current);
>  				instrumentation_end();
> diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c
> index c6d86e4da8a9..645dba919386 100644
> --- a/kernel/time/tick-sched.c
> +++ b/kernel/time/tick-sched.c
> @@ -1071,6 +1071,30 @@ static void tick_nohz_full_update_tick(struct tick_sched *ts)
>  	__tick_nohz_full_update_tick(ts, ktime_get());
>  }
>  
> +#ifdef CONFIG_NO_HZ_FULL
> +void __tick_nohz_user_enter(void)
> +{
> +	struct tick_sched *ts;
> +	int cpu;
> +
> +	lockdep_assert_irqs_disabled();
> +
> +	cpu = smp_processor_id();
> +	if (!tick_nohz_full_cpu(cpu))
> +		return;
> +
> +	ts = this_cpu_ptr(&tick_cpu_sched);
> +	if (!tick_sched_flag_test(ts, TS_FLAG_NOHZ))
> +		return;
> +
> +	if (tick_sched_flag_test(ts, TS_FLAG_STOPPED))
> +		return;
> +
> +	if (can_stop_full_tick(cpu, ts))
> +		tick_nohz_full_stop_tick(ts, cpu);
> +}
> +#endif

Looks like this can move right below the definition of
tick_nohz_full_stop_tick() to avoid more ifdef blocks.

Thanks.


> +
>  /*
>   * A pending softirq outside an IRQ (or softirq disabled section) context
>   * should be waiting for ksoftirqd to handle it. Therefore we shouldn't
> -- 
> 2.56.0
> 

-- 
Frederic Weisbecker
SUSE Labs

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 2/4] sched/idle: Don't handle polling idle as dynticks idle
  2026-11-11 11:43 ` [PATCH v2 2/4] sched/idle: Don't handle polling idle as dynticks idle Marco Crivellari
@ 2026-11-11 16:57   ` Frederic Weisbecker
  2026-11-11 16:58     ` Peter Zijlstra
  0 siblings, 1 reply; 10+ messages in thread
From: Frederic Weisbecker @ 2026-11-11 16:57 UTC (permalink / raw)
  To: Marco Crivellari
  Cc: linux-kernel, Ingo Molnar, Michal Hocko, Aaron Tomlin,
	Marcelo Tosatti, Peter Zijlstra, Juri Lelli, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, K Prateek Nayak, Paul E. McKenney,
	Anna-Maria Behnsen, Thomas Gleixner

Le Wed, Nov 11, 2026 at 12:43:48PM +0100, Marco Crivellari a écrit :
> TS_FLAG_INIDLE tells nohz whether the tick is managed by the idle loop or
> on IRQ exit. With idle=poll it is set while polling, but cpu_idle_poll()
> doesn't run the idle loop again on interrupts: a nohz_full kick or the
> expiry of a stopped tick isn't re-evaluated until something sets
> TIF_NEED_RESCHED. The tick is therefore restarted before polling, and on
> nohz_full CPUs it then keeps running after the wake-up and interrupts
> userspace.
> 
> Enter dynticks idle only on the cpuidle path, from cpuidle_idle_call(),
> and leave it before cpu_idle_poll(). While polling, the tick is then
> managed on IRQ exit as for a running task: on nohz_full CPUs it stays
> stopped if possible, and nothing is left to do on wake-up, so the task
> doesn't pay for a tick stop after waking up. On other CPUs, leaving
> dynticks idle restarts the tick as before.
> 
> tick_nohz_idle_enter() may now be called on each idle loop iteration and
> always runs with interrupts disabled; tick_nohz_idle_exit() may be called
> outside dynticks idle and with interrupts disabled.
> 
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Juri Lelli <juri.lelli@redhat.com>
> Cc: Vincent Guittot <vincent.guittot@linaro.org>
> Cc: Dietmar Eggemann <dietmar.eggemann@arm.com>
> Cc: Steven Rostedt <rostedt@goodmis.org>
> Cc: Ben Segall <bsegall@google.com>
> Cc: Mel Gorman <mgorman@suse.de>
> Cc: Valentin Schneider <vschneid@redhat.com>
> Cc: K Prateek Nayak <kprateek.nayak@amd.com>
> Cc: Paul E. McKenney <paulmck@kernel.org>
> Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
> Cc: Thomas Gleixner <tglx@kernel.org>
> Link: https://lore.kernel.org/all/aQiWfnnSzxsnwa2o@tpad/
> Suggested-by: Frederic Weisbecker <frederic@kernel.org>
> Signed-off-by: Marco Crivellari <marco.crivellari@suse.com>

Reviewed-by: Frederic Weisbecker <frederic@kernel.org>

-- 
Frederic Weisbecker
SUSE Labs

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 2/4] sched/idle: Don't handle polling idle as dynticks idle
  2026-11-11 16:57   ` Frederic Weisbecker
@ 2026-11-11 16:58     ` Peter Zijlstra
  0 siblings, 0 replies; 10+ messages in thread
From: Peter Zijlstra @ 2026-11-11 16:58 UTC (permalink / raw)
  To: Frederic Weisbecker
  Cc: Marco Crivellari, linux-kernel, Ingo Molnar, Michal Hocko,
	Aaron Tomlin, Marcelo Tosatti, Juri Lelli, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, K Prateek Nayak, Paul E. McKenney,
	Anna-Maria Behnsen, Thomas Gleixner

On Wed, Nov 11, 2026 at 05:57:02PM +0100, Frederic Weisbecker wrote:
> Le Wed, Nov 11, 2026 at 12:43:48PM +0100, Marco Crivellari a écrit :
> > TS_FLAG_INIDLE tells nohz whether the tick is managed by the idle loop or
> > on IRQ exit. With idle=poll it is set while polling, but cpu_idle_poll()
> > doesn't run the idle loop again on interrupts: a nohz_full kick or the
> > expiry of a stopped tick isn't re-evaluated until something sets
> > TIF_NEED_RESCHED. The tick is therefore restarted before polling, and on
> > nohz_full CPUs it then keeps running after the wake-up and interrupts
> > userspace.
> > 
> > Enter dynticks idle only on the cpuidle path, from cpuidle_idle_call(),
> > and leave it before cpu_idle_poll(). While polling, the tick is then
> > managed on IRQ exit as for a running task: on nohz_full CPUs it stays
> > stopped if possible, and nothing is left to do on wake-up, so the task
> > doesn't pay for a tick stop after waking up. On other CPUs, leaving
> > dynticks idle restarts the tick as before.
> > 
> > tick_nohz_idle_enter() may now be called on each idle loop iteration and
> > always runs with interrupts disabled; tick_nohz_idle_exit() may be called
> > outside dynticks idle and with interrupts disabled.
> > 
> > Cc: Peter Zijlstra <peterz@infradead.org>
> > Cc: Juri Lelli <juri.lelli@redhat.com>
> > Cc: Vincent Guittot <vincent.guittot@linaro.org>
> > Cc: Dietmar Eggemann <dietmar.eggemann@arm.com>
> > Cc: Steven Rostedt <rostedt@goodmis.org>
> > Cc: Ben Segall <bsegall@google.com>
> > Cc: Mel Gorman <mgorman@suse.de>
> > Cc: Valentin Schneider <vschneid@redhat.com>
> > Cc: K Prateek Nayak <kprateek.nayak@amd.com>
> > Cc: Paul E. McKenney <paulmck@kernel.org>
> > Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
> > Cc: Thomas Gleixner <tglx@kernel.org>
> > Link: https://lore.kernel.org/all/aQiWfnnSzxsnwa2o@tpad/
> > Suggested-by: Frederic Weisbecker <frederic@kernel.org>
> > Signed-off-by: Marco Crivellari <marco.crivellari@suse.com>
> 
> Reviewed-by: Frederic Weisbecker <frederic@kernel.org>

So because I only receive one out of four patches, I've been ignoring
this... 

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 3/4] tick/nohz: Remove tick_nohz_idle_restart_tick()
  2026-11-11 11:43 ` [PATCH v2 3/4] tick/nohz: Remove tick_nohz_idle_restart_tick() Marco Crivellari
@ 2026-11-11 16:59   ` Frederic Weisbecker
  0 siblings, 0 replies; 10+ messages in thread
From: Frederic Weisbecker @ 2026-11-11 16:59 UTC (permalink / raw)
  To: Marco Crivellari
  Cc: linux-kernel, Ingo Molnar, Michal Hocko, Aaron Tomlin,
	Marcelo Tosatti, Anna-Maria Behnsen, Thomas Gleixner

Le Wed, Nov 11, 2026 at 12:43:49PM +0100, Marco Crivellari a écrit :
> Polling idle now leaves dynticks idle through tick_nohz_idle_exit()
> before cpu_idle_poll(), which restarts the tick where needed.
> tick_nohz_idle_restart_tick() has no users left: remove it.
> 
> Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
> Cc: Thomas Gleixner <tglx@kernel.org>
> Suggested-by: Frederic Weisbecker <frederic@kernel.org>
> Signed-off-by: Marco Crivellari <marco.crivellari@suse.com>

Reviewed-by: Frederic Weisbecker <frederic@kernel.org>

-- 
Frederic Weisbecker
SUSE Labs

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 4/4] Documentation: no_hz: Add tick stop in polling idle
  2026-11-11 11:43 ` [PATCH v2 4/4] Documentation: no_hz: Add tick stop in polling idle Marco Crivellari
@ 2026-11-11 17:00   ` Frederic Weisbecker
  0 siblings, 0 replies; 10+ messages in thread
From: Frederic Weisbecker @ 2026-11-11 17:00 UTC (permalink / raw)
  To: Marco Crivellari
  Cc: linux-kernel, Ingo Molnar, Michal Hocko, Aaron Tomlin,
	Marcelo Tosatti, Anna-Maria Behnsen, Thomas Gleixner,
	Jonathan Corbet, Shuah Khan, Randy Dunlap

Le Wed, Nov 11, 2026 at 12:43:50PM +0100, Marco Crivellari a écrit :
> On nohz_full CPUs the scheduling-clock tick is stopped while polling.
> Update the documentation accordingly.
> 
> Cc: Anna-Maria Behnsen <anna-maria@linutronix.de>
> Cc: Thomas Gleixner <tglx@kernel.org>
> Cc: Jonathan Corbet <corbet@lwn.net>
> Cc: Shuah Khan <skhan@linuxfoundation.org>
> Cc: Randy Dunlap <rdunlap@infradead.org>
> Signed-off-by: Marco Crivellari <marco.crivellari@suse.com>
> ---
>  Documentation/timers/no_hz.rst | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/Documentation/timers/no_hz.rst b/Documentation/timers/no_hz.rst
> index 4006cab64079..86cea04d2a49 100644
> --- a/Documentation/timers/no_hz.rst
> +++ b/Documentation/timers/no_hz.rst
> @@ -246,6 +246,8 @@ Known Issues
>  		be even worse than that of dyntick-idle.  Furthermore,
>  		this parameter effectively disables Turbo Mode on Intel
>  		CPUs, which can significantly reduce maximum performance.
> +		Note also that on nohz_full CPUs the scheduling-clock tick
> +		is stopped while polling, as in the other idle states.

Reviewed-by: Frederic Weisbecker <frederic@kernel.org>

-- 
Frederic Weisbecker
SUSE Labs

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-11-11 17:01 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11 11:43 [PATCH v2 0/4] Stop the idle tick on polling idle on nohz_full CPUs Marco Crivellari
2026-11-11 11:43 ` [PATCH v2 1/4] tick/nohz: Re-evaluate the tick on return to user space Marco Crivellari
2026-11-11 16:25   ` Frederic Weisbecker
2026-11-11 11:43 ` [PATCH v2 2/4] sched/idle: Don't handle polling idle as dynticks idle Marco Crivellari
2026-11-11 16:57   ` Frederic Weisbecker
2026-11-11 16:58     ` Peter Zijlstra
2026-11-11 11:43 ` [PATCH v2 3/4] tick/nohz: Remove tick_nohz_idle_restart_tick() Marco Crivellari
2026-11-11 16:59   ` Frederic Weisbecker
2026-11-11 11:43 ` [PATCH v2 4/4] Documentation: no_hz: Add tick stop in polling idle Marco Crivellari
2026-11-11 17:00   ` Frederic Weisbecker

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®