Skip to content

Commit

Permalink
Merge tag 'sched-urgent-2022-06-19' of git://git.kernel.org/pub/scm/l…
Browse files Browse the repository at this point in the history
…inux/kernel/git/tip/tip

Pull scheduler fix from Thomas Gleixner:
 "A single scheduler fix plugging a race between sched_setscheduler()
  and balance_push().

  sched_setscheduler() spliced the balance callbacks accross a lock
  break which makes it possible for an interleaving schedule() to
  observe an empty list"

* tag 'sched-urgent-2022-06-19' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  sched: Fix balance_push() vs __sched_setscheduler()
  • Loading branch information
torvalds committed Jun 19, 2022
2 parents 4afb651 + 04193d5 commit 727c399
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 3 deletions.
36 changes: 33 additions & 3 deletions kernel/sched/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -4798,25 +4798,55 @@ static void do_balance_callbacks(struct rq *rq, struct callback_head *head)

static void balance_push(struct rq *rq);

/*
* balance_push_callback is a right abuse of the callback interface and plays
* by significantly different rules.
*
* Where the normal balance_callback's purpose is to be ran in the same context
* that queued it (only later, when it's safe to drop rq->lock again),
* balance_push_callback is specifically targeted at __schedule().
*
* This abuse is tolerated because it places all the unlikely/odd cases behind
* a single test, namely: rq->balance_callback == NULL.
*/
struct callback_head balance_push_callback = {
.next = NULL,
.func = (void (*)(struct callback_head *))balance_push,
};

static inline struct callback_head *splice_balance_callbacks(struct rq *rq)
static inline struct callback_head *
__splice_balance_callbacks(struct rq *rq, bool split)
{
struct callback_head *head = rq->balance_callback;

if (likely(!head))
return NULL;

lockdep_assert_rq_held(rq);
if (head)
/*
* Must not take balance_push_callback off the list when
* splice_balance_callbacks() and balance_callbacks() are not
* in the same rq->lock section.
*
* In that case it would be possible for __schedule() to interleave
* and observe the list empty.
*/
if (split && head == &balance_push_callback)
head = NULL;
else
rq->balance_callback = NULL;

return head;
}

static inline struct callback_head *splice_balance_callbacks(struct rq *rq)
{
return __splice_balance_callbacks(rq, true);
}

static void __balance_callbacks(struct rq *rq)
{
do_balance_callbacks(rq, splice_balance_callbacks(rq));
do_balance_callbacks(rq, __splice_balance_callbacks(rq, false));
}

static inline void balance_callbacks(struct rq *rq, struct callback_head *head)
Expand Down
5 changes: 5 additions & 0 deletions kernel/sched/sched.h
Original file line number Diff line number Diff line change
Expand Up @@ -1693,6 +1693,11 @@ queue_balance_callback(struct rq *rq,
{
lockdep_assert_rq_held(rq);

/*
* Don't (re)queue an already queued item; nor queue anything when
* balance_push() is active, see the comment with
* balance_push_callback.
*/
if (unlikely(head->next || rq->balance_callback == &balance_push_callback))
return;

Expand Down

0 comments on commit 727c399

Please sign in to comment.