livepatch,sched: Add livepatch task switching to cond_resched()
Subsystems (3)
Josh Poimboeuf
maintainerv2 cover livepatch,sched: Add livepatch task switching to cond_resched()
v2:
- Avoid calling klp_cond_resched_disable() in klp_cancel_transition()
- Fix race in klp_reverse_transition()
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 | 84 ++++++++++++++++++++++++++++----- kernel/sched/core.c | 64 +++++++++++++++++++++---- 6 files changed, 174 insertions(+), 27 deletions(-) create mode 100644 include/linux/livepatch_sched.h
Signature · 1 line
2.39.1
Josh Poimboeuf
maintainerPATCH v2 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.1
Josh Poimboeuf
maintainerPATCH v2 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.1
Josh Poimboeuf
maintainerPATCH v2 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 | 78 ++++++++++++++++++++++++++++----- kernel/sched/core.c | 64 +++++++++++++++++++++++---- 5 files changed, 168 insertions(+), 24 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): */
@@ -2064,6 +2065,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)
@@ -2072,6 +2076,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)
@@ -2079,20 +2084,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 +67 -11
130 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.1
Quoted 38 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.
[1] https://lore.kernel.org/lkml/[email protected]/
[2] https://lkml.kernel.org/lkml/[email protected]kernel/livepatch/transition.c +2
-7@@ -588,14 +641,10 @@ 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() or __klp_sched_try_switch() running in +* parallel with the reverse transition. */ read_lock (&tasklist_lock);for_each_process_thread (g, task)@@ -605,9 +654,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 */
kernel/livepatch/transition.c void klp_reverse_transition(void) · line 666
Quoted 10 lines
kernel/livepatch/transition.c +8
-0+/* +* Make sure all existing invocations of klp_update_patch_state() and +* __klp_sched_try_switch() see the cleared TIF_PATCH_PENDING before +* starting the reverse transition. +*/ klp_synchronize_transition (); +/* All patching has stopped, now start the reverse transition. */ + klp_transition_patch->enabled = !klp_transition_patch->enabled; + klp_target_state = !klp_target_state;
I have double checked the synchronization and we need here:
/* * Make sure klp_update_patch_state() and __klp_sched_try_switch() * see the updated klp_target_state before TIF_PATCH_PENDING * is set again in klp_start_transition(). */ smp_wmb();
The same is achieved by smp_wmb() in klp_init_transition().
Note that the extra barrier was missing here because klp_target_state was set before klp_synchronize_transition(). It was fine because klp_update_patch_state() was called on locations where a transition in any direction was always safe.
Just for record. We need to modify @klp_target_state after klp_synchronize_transition() now. The value is used by __klp_sched_try_switch() to decide when the transition is safe. It defines what functions must not be on the stack.
I am sorry that I missed this when reviewing v1. I think that I needed to see the new code with a fresh head.
kernel/livepatch/transition.c void klp_reverse_transition(void) · line 668
Quoted 2 lines
kernel/livepatch/transition.c +0
-0klp_start_transition (); }
I do not see any other problem. With the above barrier added, feel free to use:
It is for the livepatching part. I checked also the scheduler code and it looked fine but I would not put my hand in the fire for it.
Best Regards,
Petr
Josh Poimboeuf
maintainerQuoted 20 lines · Petr Mladek
Review
Quoted 3 lines
(quoted diff) +3
-0+ /* All patching has stopped, now start the reverse transition. */ + klp_transition_patch->enabled = !klp_transition_patch->enabled; + klp_target_state = !klp_target_state;I have double checked the synchronization and we need here:
/* * Make sure klp_update_patch_state() and __klp_sched_try_switch() * see the updated klp_target_state before TIF_PATCH_PENDING * is set again in klp_start_transition(). */ smp_wmb();The same is achieved by
smp_wmb()inklp_init_transition().Note that the extra barrier was missing here because
klp_target_statewas set beforeklp_synchronize_transition(). It was fine becauseklp_update_patch_state()was called on locations where a transition in any direction was always safe.
Quoted 4 lines
Just for record. We need to modify @klp_target_state after
klp_synchronize_transition()now. The value is used by__klp_sched_try_switch()to decide when the transition is safe. It defines what functions must not be on the stack.
Yes, makes sense. And we need a corresponding smp_rmb() in __klp_sched_try_switch() before the call to klp_try_switch_task(), right?
Something like this on top? Also updated a few more comments.
kernel/livepatch/core.c +1 -0
@@ -33,6 +33,7 @@* * - klp_ftrace_handler() * - klp_update_patch_state() +* - __klp_sched_try_switch() */ DEFINE_MUTEX (klp_mutex);
kernel/livepatch/transition.c +24 -5
@@ -192,8 +192,8 @@ void klp_update_patch_state(struct task_struct *task)* barrier (smp_rmb) for two cases: * * 1) Enforce the order of the TIF_PATCH_PENDING read and the -* klp_target_state read. The corresponding write barrier is in -* klp_init_transition(). +* klp_target_state read. The corresponding write barriers are in +* klp_init_transition() and klp_reverse_transition(). * * 2) Enforce the order of the TIF_PATCH_PENDING read and a future read * of func->transition, if klp_ftrace_handler() is called later on
@@ -381,6 +381,14 @@ void __klp_sched_try_switch(void)if (unlikely (!klp_patch_pending (current))) goto out; +/* +* Enforce the order of the TIF_PATCH_PENDING read above and the +* klp_target_state read in klp_try_switch_task(). The corresponding +* write barriers are in klp_init_transition() and +* klp_reverse_transition(). +*/ +smp_rmb (); +klp_try_switch_task (current); out:
@@ -604,8 +612,9 @@ void klp_init_transition(struct klp_patch *patch, int state)* see a func in transition with a task->patch_state of KLP_UNDEFINED. * * Also enforce the order of the klp_target_state write and future -* TIF_PATCH_PENDING writes to ensure klp_update_patch_state() doesn't -* set a task->patch_state to KLP_UNDEFINED. +* TIF_PATCH_PENDING writes to ensure klp_update_patch_state() and +* __klp_sched_try_switch() don't set a task->patch_state to +* KLP_UNDEFINED. */ smp_wmb ();
@@ -661,9 +670,19 @@ void klp_reverse_transition(void)*/ klp_synchronize_transition (); -/* All patching has stopped, now start the reverse transition. */ +/* All patching has stopped, now start the reverse transition: */ + klp_transition_patch->enabled = !klp_transition_patch->enabled; klp_target_state = !klp_target_state; + +/* +* Enforce the order of the klp_target_state write and the +* TIF_PATCH_PENDING writes in klp_start_transition() to ensure +* klp_update_patch_state() and __klp_sched_try_switch() don't set +* task->patch_state to the wrong value. +*/ +smp_wmb (); +klp_start_transition (); }
Petr Mladek
maintainerQuoted 21 lines · Josh Poimboeuf
Quoted 20 lines · Petr Mladek
Review
Quoted 3 lines
(quoted diff) +3
-0+ /* All patching has stopped, now start the reverse transition. */ + klp_transition_patch->enabled = !klp_transition_patch->enabled; + klp_target_state = !klp_target_state;I have double checked the synchronization and we need here:
/* * Make sure klp_update_patch_state() and __klp_sched_try_switch() * see the updated klp_target_state before TIF_PATCH_PENDING * is set again in klp_start_transition(). */ smp_wmb();The same is achieved by
smp_wmb()inklp_init_transition().Note that the extra barrier was missing here because
klp_target_statewas set beforeklp_synchronize_transition(). It was fine becauseklp_update_patch_state()was called on locations where a transition in any direction was always safe.
Quoted 8 lines
Quoted 4 lines
Just for record. We need to modify @klp_target_state after
klp_synchronize_transition()now. The value is used by__klp_sched_try_switch()to decide when the transition is safe. It defines what functions must not be on the stack.Yes, makes sense. And we need a corresponding
smp_rmb()in__klp_sched_try_switch()before the call toklp_try_switch_task(), right?
Yes. Great catch!
I feel shame that I missed this counter piece when I realized that the write barrier was missing.
Quoted 18 lines
Something like this on top? Also updated a few more comments.
kernel/livepatch/transition.c +2
-2@@ -192,8 +192,8 @@ void klp_update_patch_state(struct task_struct *task)* barrier (smp_rmb) for two cases: * * 1) Enforce the order of the TIF_PATCH_PENDING read and the -* klp_target_state read. The corresponding write barrier is in -* klp_init_transition(). +* klp_target_state read. The corresponding write barriers are in +* klp_init_transition() and klp_reverse_transition(). * * 2) Enforce the order of the TIF_PATCH_PENDING read and a future read * of func->transition, if klp_ftrace_handler() is called later on
kernel/livepatch/transition.c void __klp_sched_try_switch(void) · line 390
Quoted 10 lines
kernel/livepatch/transition.c +7
-0@@ -381,6 +381,14 @@ void __klp_sched_try_switch(void)if (unlikely (!klp_patch_pending (current))) goto out; +/* +* Enforce the order of the TIF_PATCH_PENDING read above and the +* klp_target_state read in klp_try_switch_task(). The corresponding +* write barriers are in klp_init_transition() and +* klp_reverse_transition(). +*/ +smp_rmb ();
This barrier has basically the same purpose as the implicit read barrier in klp_update_patch_state().
The comment in klp_update_patch_state() says that the read barrier actually has two purposes. The 1st one is easy. It is the one described above.
It took me quite some time to understand the 2nd purpose again. The original comment was:
* 2) Enforce the order of the TIF_PATCH_PENDING read and a future read
* of func->transition, if klp_ftrace_handler() is called later on
* the same CPU. See __klp_disable_patch().
I think that a better description would be:
* 2) Make sure that this CPU sees func->transition enabled when
* it sees the TIF_PATCH_PENDING enabled. This is important when
* the current task is transitioning itself and then calls
* klp_ftrace_handler() later. It ensures that the ftrace handler
* would check the state change that we did here.
* The corresponding write barrier is in __klp_enable_patch()
* and __klp_disable_patch().
Note that the previous comment wasn't correct. IMHO, the related write barrier is needed in both __klp_enable_patch() and __klp_disable_patch().
Quoted 17 lines
kernel/livepatch/transition.c +4
-2+klp_try_switch_task (current); out:@@ -604,8 +612,9 @@ void klp_init_transition(struct klp_patch *patch, int state)* see a func in transition with a task->patch_state of KLP_UNDEFINED. * * Also enforce the order of the klp_target_state write and future -* TIF_PATCH_PENDING writes to ensure klp_update_patch_state() doesn't -* set a task->patch_state to KLP_UNDEFINED. +* TIF_PATCH_PENDING writes to ensure klp_update_patch_state() and +* __klp_sched_try_switch() don't set a task->patch_state to +* KLP_UNDEFINED. */ smp_wmb ();
kernel/livepatch/transition.c void klp_reverse_transition(void) · line 674
Quoted 6 lines
kernel/livepatch/transition.c +2
-1@@ -661,9 +670,19 @@ void klp_reverse_transition(void)*/ klp_synchronize_transition (); -/* All patching has stopped, now start the reverse transition. */ +/* All patching has stopped, now start the reverse transition: */ +
Is the extra empty line intended?
kernel/livepatch/transition.c void klp_reverse_transition(void) · line 687
Quoted 13 lines
kernel/livepatch/transition.c +9
-0klp_transition_patch->enabled = !klp_transition_patch->enabled; klp_target_state = !klp_target_state; + +/* +* Enforce the order of the klp_target_state write and the +* TIF_PATCH_PENDING writes in klp_start_transition() to ensure +* klp_update_patch_state() and __klp_sched_try_switch() don't set +* task->patch_state to the wrong value. +*/ +smp_wmb (); +klp_start_transition (); }
This made me to revisit all the barriers in the livepatch code. The good thing is that it seems that all the barriers are correct, including the new ones proposed in this patchset.
But some comments are a bit misleading. I would like to update them a bit. I have started working on it but it goes slowly. I often get lost...
I am not sure about the ordering. I do not want to block this patchset by the clean up of the comments. The currently proposed ones are good enough. Feel free to send v3.
Or would you prefer to wait for my clean up of the comments?
Best Regards,
Petr
Josh Poimboeuf
maintainerQuoted 36 lines · Petr Mladek
kernel/livepatch/transition.c void __klp_sched_try_switch(void) · line 390
Quoted 11 lines
kernel/livepatch/transition.c +7
-0@@ -381,6 +381,14 @@ void __klp_sched_try_switch(void)if (unlikely (!klp_patch_pending (current))) goto out; +/* +* Enforce the order of the TIF_PATCH_PENDING read above and the +* klp_target_state read in klp_try_switch_task(). The corresponding +* write barriers are in klp_init_transition() and +* klp_reverse_transition(). +*/ +smp_rmb ();This barrier has basically the same purpose as the implicit read barrier in
klp_update_patch_state().The comment in
klp_update_patch_state()says that the read barrier actually has two purposes. The 1st one is easy. It is the one described above.It took me quite some time to understand the 2nd purpose again. The original comment was:
* 2) Enforce the order of the
TIF_PATCH_PENDINGread and a future read
* offunc->transition, ifklp_ftrace_handler()is called later on
* the same CPU. See__klp_disable_patch().I think that a better description would be:
* 2) Make sure that this CPU sees
func->transitionenabled when
* it sees theTIF_PATCH_PENDINGenabled. This is important when
* the current task is transitioning itself and then calls
*klp_ftrace_handler()later. It ensures that the ftrace handler
* would check the state change that we did here.
* The corresponding write barrier is in__klp_enable_patch()
* and__klp_disable_patch().
Quoted 2 lines
Note that the previous comment wasn't correct. IMHO, the related write barrier is needed in both
__klp_enable_patch()and__klp_disable_patch().
That 2nd comment also confused me. Yours is definitely better!
Quoted 9 lines
kernel/livepatch/transition.c void klp_reverse_transition(void) · line 674
Quoted 7 lines
kernel/livepatch/transition.c +2
-1@@ -661,9 +670,19 @@ void klp_reverse_transition(void)*/ klp_synchronize_transition (); -/* All patching has stopped, now start the reverse transition. */ +/* All patching has stopped, now start the reverse transition: */ +Is the extra empty line intended?
Due to the additional comment and whitespace added below, I added whitespace here to try to imply that the comment doesn't only apply to the following two lines, but also the code after it. I'm open to suggestions :-)
Quoted 9 lines
Quoted 9 lines
kernel/livepatch/transition.c +7
-0klp_transition_patch->enabled = !klp_transition_patch->enabled; klp_target_state = !klp_target_state; + +/* +* Enforce the order of the klp_target_state write and the +* TIF_PATCH_PENDING writes in klp_start_transition() to ensure +* klp_update_patch_state() and __klp_sched_try_switch() don't set +* task->patch_state to the wrong value. +*/
Quoted 8 lines
kernel/livepatch/transition.c void klp_reverse_transition(void) · line 687
Quoted 4 lines
kernel/livepatch/transition.c +2
-0+smp_wmb (); +klp_start_transition (); }This made me to revisit all the barriers in the livepatch code. The good thing is that it seems that all the barriers are correct, including the new ones proposed in this patchset.
That's good news :-)
Quoted 9 lines
But some comments are a bit misleading. I would like to update them a bit. I have started working on it but it goes slowly. I often get lost...
I am not sure about the ordering. I do not want to block this patchset by the clean up of the comments. The currently proposed ones are good enough. Feel free to send v3.
Or would you prefer to wait for my clean up of the comments?
Sounds good, I'll send v3 soon and you can base your updates on top.
Signature · 1 line
Josh