Skip to content

Commit 4e424b8

Browse files
committed
sched,livepatch: Untangle cond_resched() and live-patching
JIRA: https://issues.redhat.com/browse/RHEL-110301 Conflicts: Minor fuzz in core.c in hunk #4. commit 676e8cf Author: Peter Zijlstra <[email protected]> Date: Fri May 9 13:36:59 2025 +0200 sched,livepatch: Untangle cond_resched() and live-patching With the goal of deprecating / removing VOLUNTARY preempt, live-patch needs to stop relying on cond_resched() to make forward progress. Instead, rely on schedule() with TASK_FREEZABLE set. Just like live-patching, the freezer needs to be able to stop tasks in a safe / known state. [bigeasy: use likely() in __klp_sched_try_switch() and update comments] Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Signed-off-by: Sebastian Andrzej Siewior <[email protected]> Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Reviewed-by: Thomas Gleixner <[email protected]> Reviewed-by: Petr Mladek <[email protected]> Tested-by: Petr Mladek <[email protected]> Tested-by: Miroslav Benes <[email protected]> Acked-by: Miroslav Benes <[email protected]> Acked-by: Josh Poimboeuf <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Phil Auld <[email protected]>
1 parent c34b61e commit 4e424b8

File tree

4 files changed

+27
-92
lines changed

4 files changed

+27
-92
lines changed

include/linux/livepatch_sched.h

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,23 @@
33
#define _LINUX_LIVEPATCH_SCHED_H_
44

55
#include <linux/jump_label.h>
6-
#include <linux/static_call_types.h>
6+
#include <linux/sched.h>
77

88
#ifdef CONFIG_LIVEPATCH
99

1010
void __klp_sched_try_switch(void);
1111

12-
#if !defined(CONFIG_PREEMPT_DYNAMIC) || !defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL)
13-
1412
DECLARE_STATIC_KEY_FALSE(klp_sched_try_switch_key);
1513

16-
static __always_inline void klp_sched_try_switch(void)
14+
static __always_inline void klp_sched_try_switch(struct task_struct *curr)
1715
{
18-
if (static_branch_unlikely(&klp_sched_try_switch_key))
16+
if (static_branch_unlikely(&klp_sched_try_switch_key) &&
17+
READ_ONCE(curr->__state) & TASK_FREEZABLE)
1918
__klp_sched_try_switch();
2019
}
2120

22-
#endif /* !CONFIG_PREEMPT_DYNAMIC || !CONFIG_HAVE_PREEMPT_DYNAMIC_CALL */
23-
2421
#else /* !CONFIG_LIVEPATCH */
25-
static inline void klp_sched_try_switch(void) {}
26-
static inline void __klp_sched_try_switch(void) {}
22+
static inline void klp_sched_try_switch(struct task_struct *curr) {}
2723
#endif /* CONFIG_LIVEPATCH */
2824

2925
#endif /* _LINUX_LIVEPATCH_SCHED_H_ */

include/linux/sched.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
#include <linux/seqlock_types.h>
4646
#include <linux/kcsan.h>
4747
#include <linux/rv.h>
48-
#include <linux/livepatch_sched.h>
4948
#include <linux/uidgid_types.h>
5049
#include <linux/tracepoint-defs.h>
5150
#include <asm/kmap_size.h>
@@ -2042,9 +2041,6 @@ extern int __cond_resched(void);
20422041

20432042
#if defined(CONFIG_PREEMPT_DYNAMIC) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL)
20442043

2045-
void sched_dynamic_klp_enable(void);
2046-
void sched_dynamic_klp_disable(void);
2047-
20482044
DECLARE_STATIC_CALL(cond_resched, __cond_resched);
20492045

20502046
static __always_inline int _cond_resched(void)
@@ -2065,7 +2061,6 @@ static __always_inline int _cond_resched(void)
20652061

20662062
static inline int _cond_resched(void)
20672063
{
2068-
klp_sched_try_switch();
20692064
return __cond_resched();
20702065
}
20712066

@@ -2075,7 +2070,6 @@ static inline int _cond_resched(void)
20752070

20762071
static inline int _cond_resched(void)
20772072
{
2078-
klp_sched_try_switch();
20792073
return 0;
20802074
}
20812075

kernel/livepatch/transition.c

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,13 @@ static unsigned int klp_signals_cnt;
2929

3030
/*
3131
* When a livepatch is in progress, enable klp stack checking in
32-
* cond_resched(). This helps CPU-bound kthreads get patched.
32+
* schedule(). This helps CPU-bound kthreads get patched.
3333
*/
34-
#if defined(CONFIG_PREEMPT_DYNAMIC) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL)
35-
36-
#define klp_cond_resched_enable() sched_dynamic_klp_enable()
37-
#define klp_cond_resched_disable() sched_dynamic_klp_disable()
38-
39-
#else /* !CONFIG_PREEMPT_DYNAMIC || !CONFIG_HAVE_PREEMPT_DYNAMIC_CALL */
4034

4135
DEFINE_STATIC_KEY_FALSE(klp_sched_try_switch_key);
42-
EXPORT_SYMBOL(klp_sched_try_switch_key);
4336

44-
#define klp_cond_resched_enable() static_branch_enable(&klp_sched_try_switch_key)
45-
#define klp_cond_resched_disable() static_branch_disable(&klp_sched_try_switch_key)
46-
47-
#endif /* CONFIG_PREEMPT_DYNAMIC && CONFIG_HAVE_PREEMPT_DYNAMIC_CALL */
37+
#define klp_resched_enable() static_branch_enable(&klp_sched_try_switch_key)
38+
#define klp_resched_disable() static_branch_disable(&klp_sched_try_switch_key)
4839

4940
/*
5041
* This work can be performed periodically to finish patching or unpatching any
@@ -365,26 +356,18 @@ static bool klp_try_switch_task(struct task_struct *task)
365356

366357
void __klp_sched_try_switch(void)
367358
{
368-
if (likely(!klp_patch_pending(current)))
369-
return;
370-
371359
/*
372-
* This function is called from cond_resched() which is called in many
373-
* places throughout the kernel. Using the klp_mutex here might
374-
* deadlock.
375-
*
376-
* Instead, disable preemption to prevent racing with other callers of
377-
* klp_try_switch_task(). Thanks to task_call_func() they won't be
378-
* able to switch this task while it's running.
360+
* This function is called from __schedule() while a context switch is
361+
* about to happen. Preemption is already disabled and klp_mutex
362+
* can't be acquired.
363+
* Disabled preemption is used to prevent racing with other callers of
364+
* klp_try_switch_task(). Thanks to task_call_func() they won't be
365+
* able to switch to this task while it's running.
379366
*/
380-
preempt_disable();
367+
lockdep_assert_preemption_disabled();
381368

382-
/*
383-
* Make sure current didn't get patched between the above check and
384-
* preempt_disable().
385-
*/
386-
if (unlikely(!klp_patch_pending(current)))
387-
goto out;
369+
if (likely(!klp_patch_pending(current)))
370+
return;
388371

389372
/*
390373
* Enforce the order of the TIF_PATCH_PENDING read above and the
@@ -395,11 +378,7 @@ void __klp_sched_try_switch(void)
395378
smp_rmb();
396379

397380
klp_try_switch_task(current);
398-
399-
out:
400-
preempt_enable();
401381
}
402-
EXPORT_SYMBOL(__klp_sched_try_switch);
403382

404383
/*
405384
* Sends a fake signal to all non-kthread tasks with TIF_PATCH_PENDING set.
@@ -508,7 +487,7 @@ void klp_try_complete_transition(void)
508487
}
509488

510489
/* Done! Now cleanup the data structures. */
511-
klp_cond_resched_disable();
490+
klp_resched_disable();
512491
patch = klp_transition_patch;
513492
klp_complete_transition();
514493

@@ -560,7 +539,7 @@ void klp_start_transition(void)
560539
set_tsk_thread_flag(task, TIF_PATCH_PENDING);
561540
}
562541

563-
klp_cond_resched_enable();
542+
klp_resched_enable();
564543

565544
klp_signals_cnt = 0;
566545
}

kernel/sched/core.c

Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
#include <linux/vtime.h>
6767
#include <linux/wait_api.h>
6868
#include <linux/workqueue_api.h>
69+
#include <linux/livepatch_sched.h>
6970

7071
#ifdef CONFIG_PREEMPT_DYNAMIC
7172
# ifdef CONFIG_GENERIC_ENTRY
@@ -6600,6 +6601,8 @@ static void __sched notrace __schedule(int sched_mode)
66006601
if (sched_feat(HRTICK) || sched_feat(HRTICK_DL))
66016602
hrtick_clear(rq);
66026603

6604+
klp_sched_try_switch(prev);
6605+
66036606
local_irq_disable();
66046607
rcu_note_context_switch(preempt);
66056608

@@ -7258,7 +7261,6 @@ EXPORT_STATIC_CALL_TRAMP(might_resched);
72587261
static DEFINE_STATIC_KEY_FALSE(sk_dynamic_cond_resched);
72597262
int __sched dynamic_cond_resched(void)
72607263
{
7261-
klp_sched_try_switch();
72627264
if (!static_branch_unlikely(&sk_dynamic_cond_resched))
72637265
return 0;
72647266
return __cond_resched();
@@ -7430,16 +7432,14 @@ int sched_dynamic_mode(const char *str)
74307432
# endif
74317433

74327434
static DEFINE_MUTEX(sched_dynamic_mutex);
7433-
static bool klp_override;
74347435

74357436
static void __sched_dynamic_update(int mode)
74367437
{
74377438
/*
74387439
* Avoid {NONE,VOLUNTARY} -> FULL transitions from ever ending up in
74397440
* the ZERO state, which is invalid.
74407441
*/
7441-
if (!klp_override)
7442-
preempt_dynamic_enable(cond_resched);
7442+
preempt_dynamic_enable(cond_resched);
74437443
preempt_dynamic_enable(might_resched);
74447444
preempt_dynamic_enable(preempt_schedule);
74457445
preempt_dynamic_enable(preempt_schedule_notrace);
@@ -7448,8 +7448,7 @@ static void __sched_dynamic_update(int mode)
74487448

74497449
switch (mode) {
74507450
case preempt_dynamic_none:
7451-
if (!klp_override)
7452-
preempt_dynamic_enable(cond_resched);
7451+
preempt_dynamic_enable(cond_resched);
74537452
preempt_dynamic_disable(might_resched);
74547453
preempt_dynamic_disable(preempt_schedule);
74557454
preempt_dynamic_disable(preempt_schedule_notrace);
@@ -7460,8 +7459,7 @@ static void __sched_dynamic_update(int mode)
74607459
break;
74617460

74627461
case preempt_dynamic_voluntary:
7463-
if (!klp_override)
7464-
preempt_dynamic_enable(cond_resched);
7462+
preempt_dynamic_enable(cond_resched);
74657463
preempt_dynamic_enable(might_resched);
74667464
preempt_dynamic_disable(preempt_schedule);
74677465
preempt_dynamic_disable(preempt_schedule_notrace);
@@ -7472,8 +7470,7 @@ static void __sched_dynamic_update(int mode)
74727470
break;
74737471

74747472
case preempt_dynamic_full:
7475-
if (!klp_override)
7476-
preempt_dynamic_disable(cond_resched);
7473+
preempt_dynamic_disable(cond_resched);
74777474
preempt_dynamic_disable(might_resched);
74787475
preempt_dynamic_enable(preempt_schedule);
74797476
preempt_dynamic_enable(preempt_schedule_notrace);
@@ -7484,8 +7481,7 @@ static void __sched_dynamic_update(int mode)
74847481
break;
74857482

74867483
case preempt_dynamic_lazy:
7487-
if (!klp_override)
7488-
preempt_dynamic_disable(cond_resched);
7484+
preempt_dynamic_disable(cond_resched);
74897485
preempt_dynamic_disable(might_resched);
74907486
preempt_dynamic_enable(preempt_schedule);
74917487
preempt_dynamic_enable(preempt_schedule_notrace);
@@ -7506,36 +7502,6 @@ void sched_dynamic_update(int mode)
75067502
mutex_unlock(&sched_dynamic_mutex);
75077503
}
75087504

7509-
#ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL
7510-
7511-
static int klp_cond_resched(void)
7512-
{
7513-
__klp_sched_try_switch();
7514-
return __cond_resched();
7515-
}
7516-
7517-
void sched_dynamic_klp_enable(void)
7518-
{
7519-
mutex_lock(&sched_dynamic_mutex);
7520-
7521-
klp_override = true;
7522-
static_call_update(cond_resched, klp_cond_resched);
7523-
7524-
mutex_unlock(&sched_dynamic_mutex);
7525-
}
7526-
7527-
void sched_dynamic_klp_disable(void)
7528-
{
7529-
mutex_lock(&sched_dynamic_mutex);
7530-
7531-
klp_override = false;
7532-
__sched_dynamic_update(preempt_dynamic_mode);
7533-
7534-
mutex_unlock(&sched_dynamic_mutex);
7535-
}
7536-
7537-
#endif /* CONFIG_HAVE_PREEMPT_DYNAMIC_CALL */
7538-
75397505
static int __init setup_preempt_mode(char *str)
75407506
{
75417507
int mode = sched_dynamic_mode(str);

0 commit comments

Comments
 (0)