mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-11-01 00:48:50 +00:00
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 an interleaving schedule() observe an empty list. -----BEGIN PGP SIGNATURE----- iQJHBAABCgAxFiEEQp8+kY+LLUocC4bMphj1TA10mKEFAmKvHpETHHRnbHhAbGlu dXRyb25peC5kZQAKCRCmGPVMDXSYoUCfD/9LVsemdRGoz2Ur7LUjKMq9op7rzFmD arDTPsvb5ukbD2u+PrObo6pkAQvmSu/b/qFhLQNkGLUMWn/Ok5mq2jikwArboT3X k/BC56p7NSynJr2Xb035UcvlnLZxPn0giUo9SeGaGM4zm4rioXyrBWkmsjXmFh19 LrRTCip9Aom5F+ctCC96BOGvrsM/C3F4si7ZwEp8ZCcClmhrrZhTBEPFGKg0AuVJ qJkxgVmvuszaS7CL5ZyHiE35liyqRYx/NLldVWWFKxQcBqlGxqB/4ymB3QyecJfI VsuWr8oNZOGbjbhg7Nv/qdbDTz5dHxpEuzHx0osAFhUwQ7TSmJfh2InE1Xe0jNyU 0HXFsx2kmytLoMDuZCkzGJRaGotSsLL7ZTs2e6Ktbh4j42MGQaHYC5GbX68Doxv1 A9aoKenh658oU26kPXGI0iYo9tkToMY7U8HM56CXxaAc5+XwHKPoU2pUOUTrD0a/ eqP2Uu7QMuNzQ6M9vsD/lAm8HUlicp08h9s6wzENIwGSkJ7GdpItat5Mbh1pu7yh dL1+1q6kZCBjc9oy7THV4Oqph6Hxgg/oze9Oi9f4ocvt97GHtn1ahEOxey2i/CJR 7912p/yxbVXWOmUV6BGZ1TxjV3P9HpgWD5KPt/vOItnhMeWFU0NnxW6e0JJSV4T9 5dmPK8wH0/Nzvg== =k54w -----END PGP SIGNATURE----- Merge tag 'sched-urgent-2022-06-19' of git://git.kernel.org/pub/scm/linux/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()
This commit is contained in:
commit
727c3991df
2 changed files with 38 additions and 3 deletions
|
@ -4798,25 +4798,55 @@ static void do_balance_callbacks(struct rq *rq, struct callback_head *head)
|
||||||
|
|
||||||
static void balance_push(struct rq *rq);
|
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 = {
|
struct callback_head balance_push_callback = {
|
||||||
.next = NULL,
|
.next = NULL,
|
||||||
.func = (void (*)(struct callback_head *))balance_push,
|
.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;
|
struct callback_head *head = rq->balance_callback;
|
||||||
|
|
||||||
|
if (likely(!head))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
lockdep_assert_rq_held(rq);
|
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;
|
rq->balance_callback = NULL;
|
||||||
|
|
||||||
return head;
|
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)
|
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)
|
static inline void balance_callbacks(struct rq *rq, struct callback_head *head)
|
||||||
|
|
|
@ -1693,6 +1693,11 @@ queue_balance_callback(struct rq *rq,
|
||||||
{
|
{
|
||||||
lockdep_assert_rq_held(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))
|
if (unlikely(head->next || rq->balance_callback == &balance_push_callback))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue