Skip to contentSkip to conversation

sched/idle: disable tick in idle=poll idle entry

PATCH In review Marcelo Tosatti 3 participants 5 messages Nov 3, 2025 – Dec 10, 2025

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH v3 1/1 sched/idle: disable tick in idle=poll idle entry

Commit a5183862e76fdc25f36b39c2489b816a5c66e2e5 ("tick/nohz: Conditionally restart tick on idle exit") allows a nohz_full CPU to enter idle and return from it with the scheduler tick disabled (since the tick might be undesired noise).

The idle=poll case still unconditionally restarts the tick when entering idle.

To reduce the noise for that case as well, stop the tick when entering idle, for the idle=poll case.

Change tick_nohz_full_kick_cpu to set NEED_RESCHED bit, to handle the case where a new timer is added from an interrupt. This breaks out of cpu_idle_poll and rearms the timer if necessary.

---

v3: Add comment with proper explanation (Frederic Weisbecker)
Add signed-off-by (Thomas Gleixner)
v2: Handle the case where a new timer is added from an interrupt (Frederic Weisbecker)

 include/linux/sched.h    |    2 ++
 kernel/sched/core.c      |   10 ++++++++++
 kernel/sched/idle.c      |    2 +-
 kernel/time/tick-sched.c |    7 +++++++
 4 files changed, 20 insertions(+), 1 deletion(-)
include/linux/sched.h +2 -0
index cbb7340c5866..1f6938dc20cd 100644
@@ -2428,4 +2428,6 @@ extern void migrate_enable(void);
 
 DEFINE_LOCK_GUARD_0(migrate, migrate_disable(), migrate_enable())
 
+void set_tif_resched_if_polling(int cpu);
+
 #endif
kernel/sched/core.c +10 -0
index f1ebf67b48e2..f0b84600084b 100644
@@ -988,6 +988,11 @@ static bool set_nr_if_polling(struct task_struct *p)
 	return true;
 }
 
+void set_tif_resched_if_polling(int cpu)
+{
+	set_nr_if_polling(cpu_rq(cpu)->idle);
+}
+
 #else
 static inline bool set_nr_and_not_polling(struct thread_info *ti, int tif)
 {
@@ -999,6 +1004,11 @@ static inline bool set_nr_if_polling(struct task_struct *p)
 {
 	return false;
 }
+
+void set_tif_resched_if_polling(int cpu)
+{
+	set_tsk_need_resched(cpu_rq(cpu)->idle);
+}
 #endif
 
 static bool __wake_q_add(struct wake_q_head *head, struct task_struct *task)
kernel/sched/idle.c +1 -1
index c39b089d4f09..428c2d1cbd1b 100644
@@ -324,7 +324,7 @@ 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();
+			tick_nohz_idle_stop_tick();
 			cpu_idle_poll();
 		} else {
 			cpuidle_idle_call();
kernel/time/tick-sched.c +7 -0
index c527b421c865..9ec51da49591 100644
@@ -408,6 +408,13 @@ void tick_nohz_full_kick_cpu(int cpu)
 	if (!tick_nohz_full_cpu(cpu))
 		return;
 
+	/*
+	 * When idle=poll, with the tick disabled (therefore idle CPU looping
+	 * at cpu_idle_poll), if a new timer is added from an interrupt,
+	 * the cpu_idle_poll only exits when TIF_NEED_RESCHED gets set.
+	 */
+	set_tif_resched_if_polling(cpu);
+
 	irq_work_queue_on(&per_cpu(nohz_full_kick_work, cpu), cpu);
 }
 
maintainer
#1 lore raw
Quoted 3 lines · Marcelo Tosatti

Commit a5183862e76fdc25f36b39c2489b816a5c66e2e5 ("tick/nohz: Conditionally restart tick on idle exit") allows

Quoting a commit usually shortens the hash to 12 charters, no?

Quoted 86 lines

a nohz_full CPU to enter idle and return from it with the scheduler tick disabled (since the tick might be undesired noise).

The idle=poll case still unconditionally restarts the tick when entering idle.

To reduce the noise for that case as well, stop the tick when entering idle, for the idle=poll case.

Change tick_nohz_full_kick_cpu to set NEED_RESCHED bit, to handle the case where a new timer is added from an interrupt. This breaks out of cpu_idle_poll and rearms the timer if necessary.

---

v3: Add comment with proper explanation (Frederic Weisbecker)
Add signed-off-by (Thomas Gleixner)
v2: Handle the case where a new timer is added from an interrupt (Frederic Weisbecker)

 include/linux/sched.h    |    2 ++
 kernel/sched/core.c      |   10 ++++++++++
 kernel/sched/idle.c      |    2 +-
 kernel/time/tick-sched.c |    7 +++++++
 4 files changed, 20 insertions(+), 1 deletion(-)
include/linux/sched.h +2 -0
index cbb7340c5866..1f6938dc20cd 100644
@@ -2428,4 +2428,6 @@ extern void migrate_enable(void);
 
 DEFINE_LOCK_GUARD_0(migrate, migrate_disable(), migrate_enable())
 
+void set_tif_resched_if_polling(int cpu);
+
 #endif
kernel/sched/core.c +10 -0
index f1ebf67b48e2..f0b84600084b 100644
@@ -988,6 +988,11 @@ static bool set_nr_if_polling(struct task_struct *p)
 	return true;
 }
 
+void set_tif_resched_if_polling(int cpu)
+{
+	set_nr_if_polling(cpu_rq(cpu)->idle);
+}
+
 #else
 static inline bool set_nr_and_not_polling(struct thread_info *ti, int tif)
 {
@@ -999,6 +1004,11 @@ static inline bool set_nr_if_polling(struct task_struct *p)
 {
 	return false;
 }
+
+void set_tif_resched_if_polling(int cpu)
+{
+	set_tsk_need_resched(cpu_rq(cpu)->idle);
+}
 #endif
 
 static bool __wake_q_add(struct wake_q_head *head, struct task_struct *task)
kernel/sched/idle.c +1 -1
index c39b089d4f09..428c2d1cbd1b 100644
@@ -324,7 +324,7 @@ 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();
+			tick_nohz_idle_stop_tick();
 			cpu_idle_poll();
 		} else {
 			cpuidle_idle_call();
kernel/time/tick-sched.c +0 -0
index c527b421c865..9ec51da49591 100644
@@ -408,6 +408,13 @@ void tick_nohz_full_kick_cpu(int cpu)
 	if (!tick_nohz_full_cpu(cpu))
 		return;

kernel/time/tick-sched.c void tick_nohz_full_kick_cpu(int cpu) · line 419

Quoted 10 lines
kernel/time/tick-sched.c +7 -0
 
+	/*
+	 * When idle=poll, with the tick disabled (therefore idle CPU looping
+	 * at cpu_idle_poll), if a new timer is added from an interrupt,
+	 * the cpu_idle_poll only exits when TIF_NEED_RESCHED gets set.
+	 */
+	set_tif_resched_if_polling(cpu);
+
 	irq_work_queue_on(&per_cpu(nohz_full_kick_work, cpu), cpu);
 }

I'm confused. Why is this here and not in tick_nohz_start_idle() or something?

#2 lore raw
Quoted 94 lines · Peter Zijlstra
Quoted 3 lines · Marcelo Tosatti

Commit a5183862e76fdc25f36b39c2489b816a5c66e2e5 ("tick/nohz: Conditionally restart tick on idle exit") allows

Quoting a commit usually shortens the hash to 12 charters, no?

Quoted 87 lines

a nohz_full CPU to enter idle and return from it with the scheduler tick disabled (since the tick might be undesired noise).

The idle=poll case still unconditionally restarts the tick when entering idle.

To reduce the noise for that case as well, stop the tick when entering idle, for the idle=poll case.

Change tick_nohz_full_kick_cpu to set NEED_RESCHED bit, to handle the case where a new timer is added from an interrupt. This breaks out of cpu_idle_poll and rearms the timer if necessary.

---

v3: Add comment with proper explanation (Frederic Weisbecker)
Add signed-off-by (Thomas Gleixner)
v2: Handle the case where a new timer is added from an interrupt (Frederic Weisbecker)

 include/linux/sched.h    |    2 ++
 kernel/sched/core.c      |   10 ++++++++++
 kernel/sched/idle.c      |    2 +-
 kernel/time/tick-sched.c |    7 +++++++
 4 files changed, 20 insertions(+), 1 deletion(-)
include/linux/sched.h +2 -0
index cbb7340c5866..1f6938dc20cd 100644
@@ -2428,4 +2428,6 @@ extern void migrate_enable(void);
 
 DEFINE_LOCK_GUARD_0(migrate, migrate_disable(), migrate_enable())
 
+void set_tif_resched_if_polling(int cpu);
+
 #endif
kernel/sched/core.c +10 -0
index f1ebf67b48e2..f0b84600084b 100644
@@ -988,6 +988,11 @@ static bool set_nr_if_polling(struct task_struct *p)
 	return true;
 }
 
+void set_tif_resched_if_polling(int cpu)
+{
+	set_nr_if_polling(cpu_rq(cpu)->idle);
+}
+
 #else
 static inline bool set_nr_and_not_polling(struct thread_info *ti, int tif)
 {
@@ -999,6 +1004,11 @@ static inline bool set_nr_if_polling(struct task_struct *p)
 {
 	return false;
 }
+
+void set_tif_resched_if_polling(int cpu)
+{
+	set_tsk_need_resched(cpu_rq(cpu)->idle);
+}
 #endif
 
 static bool __wake_q_add(struct wake_q_head *head, struct task_struct *task)
kernel/sched/idle.c +1 -1
index c39b089d4f09..428c2d1cbd1b 100644
@@ -324,7 +324,7 @@ 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();
+			tick_nohz_idle_stop_tick();
 			cpu_idle_poll();
 		} else {
 			cpuidle_idle_call();
kernel/time/tick-sched.c +0 -0
index c527b421c865..9ec51da49591 100644
@@ -408,6 +408,13 @@ void tick_nohz_full_kick_cpu(int cpu)
 	if (!tick_nohz_full_cpu(cpu))
 		return;
 
Quoted 14 lines

kernel/time/tick-sched.c void tick_nohz_full_kick_cpu(int cpu) · line 419

Quoted 9 lines
kernel/time/tick-sched.c +7 -0
+	/*
+	 * When idle=poll, with the tick disabled (therefore idle CPU looping
+	 * at cpu_idle_poll), if a new timer is added from an interrupt,
+	 * the cpu_idle_poll only exits when TIF_NEED_RESCHED gets set.
+	 */
+	set_tif_resched_if_polling(cpu);
+
 	irq_work_queue_on(&per_cpu(nohz_full_kick_work, cpu), cpu);
 }

I'm confused. Why is this here and not in tick_nohz_start_idle() or something?

Hi Peter,

The codepath being followed is:

enqueue_timer -> trigger_dyntick_cpu -> wake_up_nohz_cpu -> wake_up_full_nohz_cpu -> tick_nohz_full_kick_cpu -> set_tif_resched_if_polling.

So we only set the PF_RESCHED bit if there is a pending timer on the CPU.

Calling unconditionally from tick_nohz_start_idle seems strange:

/**
* tick_nohz_idle_enter - prepare for entering idle on the current CPU *
* Called when we start the idle loop.
*/
void tick_nohz_idle_enter(void)

{
        struct tick_sched *ts;
        
        lockdep_assert_irqs_enabled();

        local_irq_disable();

        ts = this_cpu_ptr(&tick_cpu_sched);

        WARN_ON_ONCE(ts->timer_expires_base);

        tick_sched_flag_set(ts, TS_FLAG_INIDLE);
        tick_nohz_start_idle(ts);

        local_irq_enable();
}

Can test for TS_FLAG_INIDLE before calling set_tif_resched_if_polling (but seems not necessary since tick_nohz_full_kick_cpu will wake up the CPU anyway and is a slow path (timer addition)).

What do you think?

#3 lore raw
Quoted 146 lines · Marcelo Tosatti
Quoted 94 lines · Peter Zijlstra
Quoted 3 lines · Marcelo Tosatti

Commit a5183862e76fdc25f36b39c2489b816a5c66e2e5 ("tick/nohz: Conditionally restart tick on idle exit") allows

Quoting a commit usually shortens the hash to 12 charters, no?

Quoted 87 lines

a nohz_full CPU to enter idle and return from it with the scheduler tick disabled (since the tick might be undesired noise).

The idle=poll case still unconditionally restarts the tick when entering idle.

To reduce the noise for that case as well, stop the tick when entering idle, for the idle=poll case.

Change tick_nohz_full_kick_cpu to set NEED_RESCHED bit, to handle the case where a new timer is added from an interrupt. This breaks out of cpu_idle_poll and rearms the timer if necessary.

---

v3: Add comment with proper explanation (Frederic Weisbecker)
Add signed-off-by (Thomas Gleixner)
v2: Handle the case where a new timer is added from an interrupt (Frederic Weisbecker)

 include/linux/sched.h    |    2 ++
 kernel/sched/core.c      |   10 ++++++++++
 kernel/sched/idle.c      |    2 +-
 kernel/time/tick-sched.c |    7 +++++++
 4 files changed, 20 insertions(+), 1 deletion(-)
include/linux/sched.h +2 -0
index cbb7340c5866..1f6938dc20cd 100644
@@ -2428,4 +2428,6 @@ extern void migrate_enable(void);
 
 DEFINE_LOCK_GUARD_0(migrate, migrate_disable(), migrate_enable())
 
+void set_tif_resched_if_polling(int cpu);
+
 #endif
kernel/sched/core.c +10 -0
index f1ebf67b48e2..f0b84600084b 100644
@@ -988,6 +988,11 @@ static bool set_nr_if_polling(struct task_struct *p)
 	return true;
 }
 
+void set_tif_resched_if_polling(int cpu)
+{
+	set_nr_if_polling(cpu_rq(cpu)->idle);
+}
+
 #else
 static inline bool set_nr_and_not_polling(struct thread_info *ti, int tif)
 {
@@ -999,6 +1004,11 @@ static inline bool set_nr_if_polling(struct task_struct *p)
 {
 	return false;
 }
+
+void set_tif_resched_if_polling(int cpu)
+{
+	set_tsk_need_resched(cpu_rq(cpu)->idle);
+}
 #endif
 
 static bool __wake_q_add(struct wake_q_head *head, struct task_struct *task)
kernel/sched/idle.c +1 -1
index c39b089d4f09..428c2d1cbd1b 100644
@@ -324,7 +324,7 @@ 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();
+			tick_nohz_idle_stop_tick();
 			cpu_idle_poll();
 		} else {
 			cpuidle_idle_call();
kernel/time/tick-sched.c +0 -0
index c527b421c865..9ec51da49591 100644
@@ -408,6 +408,13 @@ void tick_nohz_full_kick_cpu(int cpu)
 	if (!tick_nohz_full_cpu(cpu))
 		return;
 
Quoted 14 lines

kernel/time/tick-sched.c void tick_nohz_full_kick_cpu(int cpu) · line 419

Quoted 9 lines
kernel/time/tick-sched.c +7 -0
+	/*
+	 * When idle=poll, with the tick disabled (therefore idle CPU looping
+	 * at cpu_idle_poll), if a new timer is added from an interrupt,
+	 * the cpu_idle_poll only exits when TIF_NEED_RESCHED gets set.
+	 */
+	set_tif_resched_if_polling(cpu);
+
 	irq_work_queue_on(&per_cpu(nohz_full_kick_work, cpu), cpu);
 }

I'm confused. Why is this here and not in tick_nohz_start_idle() or something?

Hi Peter,

The codepath being followed is:

enqueue_timer -> trigger_dyntick_cpu -> wake_up_nohz_cpu -> wake_up_full_nohz_cpu -> tick_nohz_full_kick_cpu -> set_tif_resched_if_polling.

So we only set the PF_RESCHED bit if there is a pending timer on the CPU.

Calling unconditionally from tick_nohz_start_idle seems strange:

/**
* tick_nohz_idle_enter - prepare for entering idle on the current CPU *
* Called when we start the idle loop.
*/
void tick_nohz_idle_enter(void)

{
        struct tick_sched *ts;
        
        lockdep_assert_irqs_enabled();

        local_irq_disable();

        ts = this_cpu_ptr(&tick_cpu_sched);

        WARN_ON_ONCE(ts->timer_expires_base);

        tick_sched_flag_set(ts, TS_FLAG_INIDLE);
        tick_nohz_start_idle(ts);

        local_irq_enable();
}
Quoted 5 lines

Can test for TS_FLAG_INIDLE before calling set_tif_resched_if_polling (but seems not necessary since tick_nohz_full_kick_cpu will wake up the CPU anyway and is a slow path (timer addition)).

What do you think?

OK, it looks like there are no further comments on this patch.

Frederic, Peter, Thomas, can you ACK ???

Thanks

#4 lore raw
In reply to Marcelo Tosatti
Quoted 74 lines · Marcelo Tosatti

Commit a5183862e76fdc25f36b39c2489b816a5c66e2e5 ("tick/nohz: Conditionally restart tick on idle exit") allows a nohz_full CPU to enter idle and return from it with the scheduler tick disabled (since the tick might be undesired noise).

The idle=poll case still unconditionally restarts the tick when entering idle.

To reduce the noise for that case as well, stop the tick when entering idle, for the idle=poll case.

Change tick_nohz_full_kick_cpu to set NEED_RESCHED bit, to handle the case where a new timer is added from an interrupt. This breaks out of cpu_idle_poll and rearms the timer if necessary.

---

v3: Add comment with proper explanation (Frederic Weisbecker)
Add signed-off-by (Thomas Gleixner)
v2: Handle the case where a new timer is added from an interrupt (Frederic Weisbecker)

 include/linux/sched.h    |    2 ++
 kernel/sched/core.c      |   10 ++++++++++
 kernel/sched/idle.c      |    2 +-
 kernel/time/tick-sched.c |    7 +++++++
 4 files changed, 20 insertions(+), 1 deletion(-)
include/linux/sched.h +2 -0
index cbb7340c5866..1f6938dc20cd 100644
@@ -2428,4 +2428,6 @@ extern void migrate_enable(void);
 
 DEFINE_LOCK_GUARD_0(migrate, migrate_disable(), migrate_enable())
 
+void set_tif_resched_if_polling(int cpu);
+
 #endif
kernel/sched/core.c +10 -0
index f1ebf67b48e2..f0b84600084b 100644
@@ -988,6 +988,11 @@ static bool set_nr_if_polling(struct task_struct *p)
 	return true;
 }
 
+void set_tif_resched_if_polling(int cpu)
+{
+	set_nr_if_polling(cpu_rq(cpu)->idle);
+}
+
 #else
 static inline bool set_nr_and_not_polling(struct thread_info *ti, int tif)
 {
@@ -999,6 +1004,11 @@ static inline bool set_nr_if_polling(struct task_struct *p)
 {
 	return false;
 }
+
+void set_tif_resched_if_polling(int cpu)
+{
+	set_tsk_need_resched(cpu_rq(cpu)->idle);
+}
 #endif
 
 static bool __wake_q_add(struct wake_q_head *head, struct task_struct *task)
kernel/sched/idle.c +0 -0
index c39b089d4f09..428c2d1cbd1b 100644

kernel/sched/idle.c static void do_idle(void) · line 328

Quoted 6 lines
kernel/sched/idle.c +1 -1
@@ -324,7 +324,7 @@ 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();
+			tick_nohz_idle_stop_tick();
 			cpu_idle_poll();

I still think that the polling probably should happen in userspace rather than in kernelspace. But if we have to go down that road, the tick stop here should only happen if the CPU is nohz_full. Namely we should have tick_nohz_full_idle_stop_tick() and provide a comment why we are doing that.

Because aside nohz_full, stopping the tick here is useless and can make things worse for realtime workloads which may spend time reprogramming the tick while a high prio task is waiting for the CPU.

Thanks.

Signature · 2 lines
Frederic Weisbecker
SUSE Labs