livepatch,sched: Add livepatch task switching to cond_resched()
Subsystems (3)
Josh Poimboeuf
maintainercover livepatch,sched: Add livepatch task switching to cond_resched()
Fix patching stalls caused by busy kthreads.
Josh Poimboeuf (3):
livepatch: Skip task_call_func() for current task livepatch,sched: Add livepatch task switching to cond_resched()
vhost: Fix livepatch timeouts in vhost_worker()
drivers/vhost/vhost.c | 3 +- include/linux/livepatch.h | 1 + include/linux/livepatch_sched.h | 29 +++++++++++++++ include/linux/sched.h | 20 ++++++++--- kernel/livepatch/transition.c | 60 ++++++++++++++++++++++++++++++- kernel/sched/core.c | 64 ++++++++++++++++++++++++++++----- 6 files changed, 161 insertions(+), 16 deletions(-) create mode 100644 include/linux/livepatch_sched.h
Signature · 1 line
2.39.0
Josh Poimboeuf
maintainerPATCH 1/3 livepatch: Skip task_call_func() for current task
The current task doesn't need the scheduler's protection to unwind its own stack.
--- kernel/livepatch/transition.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
kernel/livepatch/transition.c +5 -1
@@ -307,7 +307,11 @@ static bool klp_try_switch_task(struct task_struct *task)* functions. If all goes well, switch the task to the target patch * state. */ - ret =task_call_func (task, klp_check_and_switch_task, &old_name); +if (task == current) + ret =klp_check_and_switch_task (current, &old_name); +else + ret =task_call_func (task, klp_check_and_switch_task, &old_name); +switch (ret) {case 0 :/* success */ break ;
Signature · 1 line
2.39.0
Josh Poimboeuf
maintainerPATCH 2/3 livepatch,sched: Add livepatch task switching to cond_resched()
In reply to Josh PoimboeufThere have been reports [1][2] of live patches failing to complete within a reasonable amount of time due to CPU-bound kthreads.
Fix it by patching tasks in cond_resched().
There are four different flavors of cond_resched(), depending on the kernel configuration. Hook into all of them.
A more elegant solution might be to use a preempt notifier. However, non-ORC unwinders can't unwind a preempted task reliably.
[1] https://lore.kernel.org/lkml/[email protected]/
[2] https://lkml.kernel.org/lkml/[email protected]
--- include/linux/livepatch.h | 1 + include/linux/livepatch_sched.h | 29 +++++++++++++++ include/linux/sched.h | 20 ++++++++--- kernel/livepatch/transition.c | 54 ++++++++++++++++++++++++++++ kernel/sched/core.c | 64 ++++++++++++++++++++++++++++----- 5 files changed, 155 insertions(+), 13 deletions(-) create mode 100644 include/linux/livepatch_sched.h
include/linux/livepatch.h +1 -0
@@ -13,6 +13,7 @@#include <linux/ftrace.h> #include <linux/completion.h> #include <linux/list.h> +#include <linux/livepatch_sched.h> #if IS_ENABLED (CONFIG_LIVEPATCH )
include/linux/livepatch_sched.h new file +29 -0
@@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _LINUX_LIVEPATCH_SCHED_H_ +#define _LINUX_LIVEPATCH_SCHED_H_ + +#include <linux/jump_label.h> +#include <linux/static_call_types.h> + +#ifdef CONFIG_LIVEPATCH + +void __klp_sched_try_switch (void ); + +#if !defined(CONFIG_PREEMPT_DYNAMIC ) || !defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL ) + +DECLARE_STATIC_KEY_FALSE (klp_sched_try_switch_key); + +static __always_inline void klp_sched_try_switch(void) +{ +if (static_branch_unlikely (&klp_sched_try_switch_key)) +__klp_sched_try_switch (); +} + +#endif /* !CONFIG_PREEMPT_DYNAMIC || !CONFIG_HAVE_PREEMPT_DYNAMIC_CALL */ + +#else /* !CONFIG_LIVEPATCH */ +static inline void klp_sched_try_switch (void ) {} +static inline void __klp_sched_try_switch (void ) {} +#endif /* CONFIG_LIVEPATCH */ + +#endif /* _LINUX_LIVEPATCH_SCHED_H_ */
include/linux/sched.h +15 -5
@@ -36,6 +36,7 @@#include <linux/seqlock.h> #include <linux/kcsan.h> #include <linux/rv.h> +#include <linux/livepatch_sched.h> #include <asm/kmap_size.h> /* task_struct member predeclarations (sorted alphabetically): */
@@ -2070,6 +2071,9 @@ extern int __cond_resched(void);#if defined(CONFIG_PREEMPT_DYNAMIC ) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL ) +void sched_dynamic_klp_enable (void ); +void sched_dynamic_klp_disable (void ); +DECLARE_STATIC_CALL (cond_resched, __cond_resched); static __always_inline int _cond_resched(void)
@@ -2078,6 +2082,7 @@ static __always_inline int _cond_resched(void) }#elif defined(CONFIG_PREEMPT_DYNAMIC ) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY ) +extern int dynamic_cond_resched (void ); static __always_inline int _cond_resched(void)
@@ -2085,20 +2090,25 @@ static __always_inline int _cond_resched(void)return dynamic_cond_resched (); } -#else +#else /* !CONFIG_PREEMPTION */ static inline int _cond_resched (void ) { +klp_sched_try_switch ();return __cond_resched (); } -#endif /* CONFIG_PREEMPT_DYNAMIC */ +#endif /* PREEMPT_DYNAMIC && CONFIG_HAVE_PREEMPT_DYNAMIC_CALL */ -#else +#else /* CONFIG_PREEMPTION && !CONFIG_PREEMPT_DYNAMIC */ -static inline int _cond_resched (void ) {return 0 ; } +static inline int _cond_resched (void ) +{ +klp_sched_try_switch (); +return 0 ; +} -#endif /* !defined(CONFIG_PREEMPTION) || defined(CONFIG_PREEMPT_DYNAMIC) */ +#endif /* !CONFIG_PREEMPTION || CONFIG_PREEMPT_DYNAMIC */ #define cond_resched () ({ \ __might_resched(__FILE__, __LINE__, 0); \
kernel/livepatch/transition.c +54 -0
84 lines not on this page · raw message
kernel/sched/core.c +56 -8
111 lines not on this page · raw message
Signature · 1 line
2.39.0
Josh Poimboeuf
maintainerPATCH 3/3 vhost: Fix livepatch timeouts in vhost_worker()
In reply to Josh PoimboeufLivepatch timeouts were reported due to busy vhost_worker() kthreads. Now that cond_resched() can do livepatch task switching, use cond_resched() in vhost_worker(). That's the better way to conditionally call schedule() anyway.
--- drivers/vhost/vhost.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
drivers/vhost/vhost.c +1 -2
@@ -363,8 +363,7 @@ static int vhost_worker(void *data)kcov_remote_start_common (dev->kcov_handle); work->fn (work);kcov_remote_stop (); -if (need_resched ()) -schedule (); +cond_resched (); } }kthread_unuse_mm (dev->mm);
Signature · 1 line
2.39.0
Quoted 6 lines · Josh Poimboeuf
Fix patching stalls caused by busy kthreads.
Josh Poimboeuf (3):
livepatch: Skiptask_call_func()for current task livepatch,sched: Add livepatch task switching tocond_resched()
vhost: Fix livepatch timeouts invhost_worker()
This is working perfectly in my testing. The busy vhost threads get switched immediately. Thanks for working up these patches!
Thanks,
Seth
Peter Zijlstra
maintainerQuoted 6 lines · Josh Poimboeuf
Fix patching stalls caused by busy kthreads.
Josh Poimboeuf (3):
livepatch: Skiptask_call_func()for current task livepatch,sched: Add livepatch task switching tocond_resched()
vhost: Fix livepatch timeouts invhost_worker()
Seems reasonable, you want me to take them through the sched tree?
Josh Poimboeuf
maintainerQuoted 9 lines · Peter Zijlstra
Quoted 6 lines · Josh Poimboeuf
Fix patching stalls caused by busy kthreads.
Josh Poimboeuf (3):
livepatch: Skiptask_call_func()for current task livepatch,sched: Add livepatch task switching tocond_resched()
vhost: Fix livepatch timeouts invhost_worker()Seems reasonable, you want me to take them through the sched tree?
That would be great, though first I'd like to get an ack from at least another livepatch maintainer.
Signature · 1 line
Josh
Re: [PATCH 1/3] livepatch: Skip task_call_func() for current task
In reply to Josh PoimboeufQuoted 4 lines · Josh Poimboeuf
The current task doesn't need the scheduler's protection to unwind its own stack.
Signed-off-by Josh Poimboeuf <[email protected]>
I do not see any problem with it:
Best Regards,
Petr
Petr Mladek
maintainerQuoted 14 lines · Josh Poimboeuf
There have been reports [1][2] of live patches failing to complete within a reasonable amount of time due to CPU-bound kthreads.
Fix it by patching tasks in
cond_resched().There are four different flavors of
cond_resched(), depending on the kernel configuration. Hook into all of them.A more elegant solution might be to use a preempt notifier. However, non-ORC unwinders can't unwind a preempted task reliably.
include/linux/sched.h static __always_inline int _cond_resched(void) · line 2098
Quoted 10 lines
include/linux/sched.h +2
-1@@ -2085,20 +2090,25 @@ static __always_inline int _cond_resched(void) return dynamic_cond_resched(); } -#else +#else /* !CONFIG_PREEMPTION */ static inline int _cond_resched (void ) { +klp_sched_try_switch ();return __cond_resched ();
My only concern is if it might cause any performance problems.
On one hand, cond_resched() is used in code paths that are slow on its own. Also it will do nothing most of the time.
On the other hand, cond_resched() is typically used in cycles. One cycle might be fast. The code might be slow because there are too many cycles. Repeating the same failing test might prolong the time significantly.
An idea is to try the switch only when it was not done during a real schedule. Something like:
static inline int _cond_resched(void)
{
int scheduled;
scheduled = __cond_resched();
if (scheduled)
klp_sched_try_switch();
return scheduled();
}But it would make it less reliable/predictable. Also it won't work in configurations when cond_resched() is always a nop.
I am probably too careful. We might keep it simple until any real life problems are reported.
kernel/livepatch/transition.c static void klp_complete_transition(void) · line 100
Quoted 8 lines
kernel/livepatch/transition.c +2
-0@@ -76,6 +96,8 @@ static void klp_complete_transition(void) klp_transition_patch->mod->name, klp_target_state == KLP_PATCHED ? "patching" : "unpatching"); +klp_cond_resched_disable (); +
Nit: Strictly speaking, this is not needed when klp_complete_transition()
is called from klp_cancel_transition(). In this case, klp_cond_resched_enable() was not called. So it might be moved into klp_try_complete_transition().
More important thing, thinking loudly:
We need to make sure that no task is in the middle klp_cond_resched_disable() when we modify anything that is used there.
We seem to be on the safe side in klp_complete_transition(). We are here only when all tasks have TIF_PATCH_PENDING cleared. In this case, __klp_sched_try_switch() just returns. Also it calls klp_synchronize_transition() so that all tasks finish the critical part in __klp_sched_try_switch() before any new transition starts.
But it is not the case in klp_reverse_transition(). It modifies klp_target_state() when __klp_sched_try_switch might be in the middle of klp_check_stack() and it might give wrong result.
klp_reverse_transition() already solves similar race with klp_update_patch_state() by clearing all TIF_PATCH_PENDING flags and calling klp_synchronize_transition(). We just need to do it earlier. Something like:
kernel/livepatch/transition.c +10 -6
@@ -642,14 +642,11 @@ void klp_reverse_transition(void) klp_target_state ==KLP_PATCHED ?"patching to unpatching" : "unpatching to patching"); - klp_transition_patch->enabled = !klp_transition_patch->enabled; - - klp_target_state = !klp_target_state;/* * Clear all TIF_PATCH_PENDING flags to prevent races caused by -* klp_update_patch_state() running in parallel with -* klp_start_transition(). +* klp_update_patch_state() and __klp_sched_try_switch() +* running in parallel. */ read_lock (&tasklist_lock);for_each_process_thread (g, task)
@@ -659,9 +656,16 @@ void klp_reverse_transition(void)for_each_possible_cpu (cpu)clear_tsk_thread_flag (idle_task (cpu),TIF_PATCH_PENDING ); -/* Let any remaining calls to klp_update_patch_state() complete */ +/* +* Make sure that both klp_update_patch_state() and +* __klp_sched_try_switch() see the TIF_PATCH_PENDING cleared. +*/ klp_synchronize_transition (); + klp_transition_patch->enabled = !klp_transition_patch->enabled; + + klp_target_state = !klp_target_state; +klp_start_transition (); }
kernel/livepatch/transition.c static void klp_complete_transition(void) · line 103
Quoted 3 lines
kernel/livepatch/transition.c +0
-0if (klp_transition_patch->replace && klp_target_state ==KLP_PATCHED ) {klp_unpatch_replaced_patches (klp_transition_patch);klp_discard_nops (klp_transition_patch);
Otherwise, the patch looks fine. I looked at it primary from the livepatching code side.
Best Regards,
Petr
Re: [PATCH 3/3] vhost: Fix livepatch timeouts in vhost_worker()
In reply to Josh PoimboeufQuoted 8 lines · Josh Poimboeuf
Livepatch timeouts were reported due to busy
vhost_worker()kthreads. Now thatcond_resched()can do livepatch task switching, usecond_resched()invhost_worker(). That's the better way to conditionally callschedule()anyway.
Best Regards,
Petr
Josh Poimboeuf
maintainerQuoted 7 lines · Petr Mladek
Review
Quoted 4 lines
static inline int
_cond_resched(void)
{(quoted diff) +1
-0+ klp_sched_try_switch(); return __cond_resched();My only concern is if it might cause any performance problems.
Quoted 7 lines
On one hand,
cond_resched()is used in code paths that are slow on its own. Also it will do nothing most of the time.On the other hand,
cond_resched()is typically used in cycles. One cycle might be fast. The code might be slow because there are too many cycles. Repeating the same failing test might prolong the time significantly.
Yes, but it should hopefully be very rare to patch a function in the call stack of a kthread loop. In general it's a good idea for the patch author to avoid that.
Quoted 14 lines
An idea is to try the switch only when it was not done during a real schedule. Something like:
static inline int
_cond_resched(void){ int scheduled; scheduled = __cond_resched(); if (scheduled) klp_sched_try_switch(); return scheduled(); }
Quoted 5 lines
But it would make it less reliable/predictable. Also it won't work in configurations when
cond_resched()is always a nop.I am probably too careful. We might keep it simple until any real life problems are reported.
If we can get away with it, I much prefer the simple unconditional klp_sched_try_switch() because of the predictability and quickness with which the kthread gets patched.
Quoted 6 lines
Quoted 6 lines
kernel/livepatch/transition.c +0
-0@@ -76,6 +96,8 @@ static void klp_complete_transition(void) klp_transition_patch->mod->name, klp_target_state == KLP_PATCHED ? "patching" : "unpatching");
Quoted 7 lines
kernel/livepatch/transition.c static void klp_complete_transition(void) · line 100
Quoted 2 lines
kernel/livepatch/transition.c +2
-0+klp_cond_resched_disable (); +Nit: Strictly speaking, this is not needed when
klp_complete_transition()
is called fromklp_cancel_transition(). In this case,klp_cond_resched_enable()was not called. So it might be moved intoklp_try_complete_transition().
Argh, I always forget about that pesky klp_cancel_transition().
Quoted 11 lines
More important thing, thinking loudly:
We need to make sure that no task is in the middle
klp_cond_resched_disable()when we modify anything that is used there.We seem to be on the safe side in
klp_complete_transition(). We are here only when all tasks haveTIF_PATCH_PENDINGcleared. In this case,__klp_sched_try_switch()just returns. Also it callsklp_synchronize_transition()so that all tasks finish the critical part in__klp_sched_try_switch()before any new transition starts.
Quoted 8 lines
But it is not the case in
klp_reverse_transition(). It modifiesklp_target_state()when__klp_sched_try_switchmight be in the middle ofklp_check_stack()and it might give wrong result.
klp_reverse_transition()already solves similar race withklp_update_patch_state()by clearing allTIF_PATCH_PENDINGflags and callingklp_synchronize_transition(). We just need to do it earlier. Something like:
Yes! Thanks, I can always count on you to find the race conditions ;-)
This highlights the similarities between klp_target_state(current) and __klp_sched_try_switch(), they both access TIF_PATCH_PENDING out-of-band.
Also, I'll update the comment in klp_copy_process(). It should be safe for with __klp_sched_try_switch() for the same reason as klp_update_patch_state(current): they all only work on 'current'.
Signature · 1 line
Josh
Petr Mladek
maintainerQuoted 11 lines · Josh Poimboeuf
Quoted 10 lines · Petr Mladek
Review
Quoted 4 lines
static inline int
_cond_resched(void)
{(quoted diff) +1
-0+ klp_sched_try_switch(); return __cond_resched();My only concern is if it might cause any performance problems.
On one hand,
cond_resched()is used in code paths that are slow on its own. Also it will do nothing most of the time.
Quoted 8 lines
Quoted 4 lines
On the other hand,
cond_resched()is typically used in cycles. One cycle might be fast. The code might be slow because there are too many cycles. Repeating the same failing test might prolong the time significantly.Yes, but it should hopefully be very rare to patch a function in the call stack of a kthread loop. In general it's a good idea for the patch author to avoid that.
I do not have any data about it.
Quoted 17 lines
Quoted 17 lines
An idea is to try the switch only when it was not done during a real schedule. Something like:
static inline int
_cond_resched(void){ int scheduled; scheduled = __cond_resched(); if (scheduled) klp_sched_try_switch(); return scheduled(); }But it would make it less reliable/predictable. Also it won't work in configurations when
cond_resched()is always a nop.
Quoted 6 lines
Quoted 2 lines
I am probably too careful. We might keep it simple until any real life problems are reported.
If we can get away with it, I much prefer the simple unconditional
klp_sched_try_switch()because of the predictability and quickness with which the kthread gets patched.
I am fine with keeping it simple now. We could always change it when it causes problems.
I primary wanted to point out the potential problem and check what others think about it.
Best Regards,
Petr