softirq: Use a dedicated thread for timer wakeups on PREEMPT_RT.

The timer and hrtimer soft interrupts are raised in hard interrupt
context. With threaded interrupts force enabled or on PREEMPT_RT this leads
to waking the ksoftirqd for the processing of the soft interrupt.

ksoftirqd runs as SCHED_OTHER task which means it will compete with other
tasks for CPU resources.  This can introduce long delays for timer
processing on heavy loaded systems and is not desired.

Split the TIMER_SOFTIRQ and HRTIMER_SOFTIRQ processing into a dedicated
timers thread and let it run at the lowest SCHED_FIFO priority.
Wake-ups for RT tasks happen from hardirq context so only timer_list timers
and hrtimers for "regular" tasks are processed here. The higher priority
ensures that wakeups are performed before scheduling SCHED_OTHER tasks.

Using a dedicated variable to store the pending softirq bits values ensure
that the timer are not accidentally picked up by ksoftirqd and other
threaded interrupts.

It shouldn't be picked up by ksoftirqd since it runs at lower priority.
However if ksoftirqd is already running while a timer fires, then ksoftird
will be PI-boosted due to the BH-lock to ktimer's priority.

The timer thread can pick up pending softirqs from ksoftirqd but only
if the softirq load is high. It is not be desired that the picked up
softirqs are processed at SCHED_FIFO priority under high softirq load
but this can already happen by a PI-boost by a force-threaded interrupt.

[ frederic@kernel.org: rcutorture.c fixes, storm fix by introduction of
  local_timers_pending() for tick_nohz_next_event() ]

[ junxiao.chang@intel.com: Ensure ktimersd gets woken up even if a
  softirq is currently served. ]

Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Paul E. McKenney <paulmck@kernel.org> [rcutorture]
Reviewed-by: Frederic Weisbecker <frederic@kernel.org>
Link: https://lore.kernel.org/all/20241106150419.2593080-4-bigeasy@linutronix.de
This commit is contained in:
Sebastian Andrzej Siewior 2024-11-06 15:51:39 +01:00 committed by Thomas Gleixner
parent a02976cfce
commit 49a1763950
6 changed files with 127 additions and 5 deletions

View File

@ -616,6 +616,53 @@ extern void __raise_softirq_irqoff(unsigned int nr);
extern void raise_softirq_irqoff(unsigned int nr);
extern void raise_softirq(unsigned int nr);
/*
* With forced-threaded interrupts enabled a raised softirq is deferred to
* ksoftirqd unless it can be handled within the threaded interrupt. This
* affects timer_list timers and hrtimers which are explicitly marked with
* HRTIMER_MODE_SOFT.
* With PREEMPT_RT enabled more hrtimers are moved to softirq for processing
* which includes all timers which are not explicitly marked HRTIMER_MODE_HARD.
* Userspace controlled timers (like the clock_nanosleep() interface) is divided
* into two categories: Tasks with elevated scheduling policy including
* SCHED_{FIFO|RR|DL} and the remaining scheduling policy. The tasks with the
* elevated scheduling policy are woken up directly from the HARDIRQ while all
* other wake ups are delayed to softirq and so to ksoftirqd.
*
* The ksoftirqd runs at SCHED_OTHER policy at which it should remain since it
* handles the softirq in an overloaded situation (not handled everything
* within its last run).
* If the timers are handled at SCHED_OTHER priority then they competes with all
* other SCHED_OTHER tasks for CPU resources are possibly delayed.
* Moving timers softirqs to a low priority SCHED_FIFO thread instead ensures
* that timer are performed before scheduling any SCHED_OTHER thread.
*/
DECLARE_PER_CPU(struct task_struct *, ktimerd);
DECLARE_PER_CPU(unsigned long, pending_timer_softirq);
void raise_ktimers_thread(unsigned int nr);
static inline unsigned int local_timers_pending_force_th(void)
{
return __this_cpu_read(pending_timer_softirq);
}
static inline void raise_timer_softirq(unsigned int nr)
{
lockdep_assert_in_irq();
if (force_irqthreads())
raise_ktimers_thread(nr);
else
__raise_softirq_irqoff(nr);
}
static inline unsigned int local_timers_pending(void)
{
if (force_irqthreads())
return local_timers_pending_force_th();
else
return local_softirq_pending();
}
DECLARE_PER_CPU(struct task_struct *, ksoftirqd);
static inline struct task_struct *this_cpu_ksoftirqd(void)

View File

@ -2440,6 +2440,14 @@ static int rcutorture_booster_init(unsigned int cpu)
WARN_ON_ONCE(!t);
sp.sched_priority = 2;
sched_setscheduler_nocheck(t, SCHED_FIFO, &sp);
#ifdef CONFIG_IRQ_FORCED_THREADING
if (force_irqthreads()) {
t = per_cpu(ktimerd, cpu);
WARN_ON_ONCE(!t);
sp.sched_priority = 2;
sched_setscheduler_nocheck(t, SCHED_FIFO, &sp);
}
#endif
}
/* Don't allow time recalculation while creating a new task. */

View File

@ -624,6 +624,24 @@ static inline void tick_irq_exit(void)
#endif
}
#ifdef CONFIG_IRQ_FORCED_THREADING
DEFINE_PER_CPU(struct task_struct *, ktimerd);
DEFINE_PER_CPU(unsigned long, pending_timer_softirq);
static void wake_timersd(void)
{
struct task_struct *tsk = __this_cpu_read(ktimerd);
if (tsk)
wake_up_process(tsk);
}
#else
static inline void wake_timersd(void) { }
#endif
static inline void __irq_exit_rcu(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
@ -636,6 +654,10 @@ static inline void __irq_exit_rcu(void)
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) && force_irqthreads() &&
local_timers_pending_force_th() && !(in_nmi() | in_hardirq()))
wake_timersd();
tick_irq_exit();
}
@ -971,12 +993,57 @@ static struct smp_hotplug_thread softirq_threads = {
.thread_comm = "ksoftirqd/%u",
};
#ifdef CONFIG_IRQ_FORCED_THREADING
static void ktimerd_setup(unsigned int cpu)
{
/* Above SCHED_NORMAL to handle timers before regular tasks. */
sched_set_fifo_low(current);
}
static int ktimerd_should_run(unsigned int cpu)
{
return local_timers_pending_force_th();
}
void raise_ktimers_thread(unsigned int nr)
{
trace_softirq_raise(nr);
__this_cpu_or(pending_timer_softirq, BIT(nr));
}
static void run_ktimerd(unsigned int cpu)
{
unsigned int timer_si;
ksoftirqd_run_begin();
timer_si = local_timers_pending_force_th();
__this_cpu_write(pending_timer_softirq, 0);
or_softirq_pending(timer_si);
__do_softirq();
ksoftirqd_run_end();
}
static struct smp_hotplug_thread timer_thread = {
.store = &ktimerd,
.setup = ktimerd_setup,
.thread_should_run = ktimerd_should_run,
.thread_fn = run_ktimerd,
.thread_comm = "ktimers/%u",
};
#endif
static __init int spawn_ksoftirqd(void)
{
cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,
takeover_tasklets);
BUG_ON(smpboot_register_percpu_thread(&softirq_threads));
#ifdef CONFIG_IRQ_FORCED_THREADING
if (force_irqthreads())
BUG_ON(smpboot_register_percpu_thread(&timer_thread));
#endif
return 0;
}
early_initcall(spawn_ksoftirqd);

View File

@ -1811,7 +1811,7 @@ void hrtimer_interrupt(struct clock_event_device *dev)
if (!ktime_before(now, cpu_base->softirq_expires_next)) {
cpu_base->softirq_expires_next = KTIME_MAX;
cpu_base->softirq_activated = 1;
__raise_softirq_irqoff(HRTIMER_SOFTIRQ);
raise_timer_softirq(HRTIMER_SOFTIRQ);
}
__hrtimer_run_queues(cpu_base, now, flags, HRTIMER_ACTIVE_HARD);
@ -1906,7 +1906,7 @@ void hrtimer_run_queues(void)
if (!ktime_before(now, cpu_base->softirq_expires_next)) {
cpu_base->softirq_expires_next = KTIME_MAX;
cpu_base->softirq_activated = 1;
__raise_softirq_irqoff(HRTIMER_SOFTIRQ);
raise_timer_softirq(HRTIMER_SOFTIRQ);
}
__hrtimer_run_queues(cpu_base, now, flags, HRTIMER_ACTIVE_HARD);

View File

@ -859,7 +859,7 @@ static void tick_nohz_restart(struct tick_sched *ts, ktime_t now)
static inline bool local_timer_softirq_pending(void)
{
return local_softirq_pending() & BIT(TIMER_SOFTIRQ);
return local_timers_pending() & BIT(TIMER_SOFTIRQ);
}
/*

View File

@ -2499,7 +2499,7 @@ static void run_local_timers(void)
*/
if (time_after_eq(jiffies, READ_ONCE(base->next_expiry)) ||
(i == BASE_DEF && tmigr_requires_handle_remote())) {
__raise_softirq_irqoff(TIMER_SOFTIRQ);
raise_timer_softirq(TIMER_SOFTIRQ);
return;
}
}