sched/dl: Prevent initialization of already set dl_timer
PATCH 1/1 sched/dl: Prevent initialization of already set dl_timer
__setparam_dl() may be called for a task which is already of deadline class. If the task is throttled, we corrupt its dl_timer when we are doing hrtimer_init() in init_dl_task_timer().
It seems that__setparam_dl() is not the place where we want to use cancel_dl_timer(). It may unlock rq while the call chain is long, and some of previous functions in the chain may be unhappy for this reason (now and in the future). So, we just try to cancel the timer, and in some situations we fail to do that.
If hrtimer_try_to_cancel() fails, than the handler is being executed on other processor, and it's waiting for rq->lock. The most probably the handler will take the lock right after we release it.
So, let's pass the actions, which we do here usually here, to the handler. It will unthrottle and queue us properly.
--- include/linux/sched/deadline.h | 2 ++ kernel/sched/core.c | 21 +++++++++++++++++---- kernel/sched/deadline.c | 10 +--------- kernel/sched/sched.h | 1 - 4 files changed, 20 insertions(+), 14 deletions(-)
include/linux/sched/deadline.h +2 -0
@@ -21,4 +21,6 @@ static inline int dl_task(struct task_struct *p) return dl_prio(p->prio); } +extern enum hrtimer_restart dl_task_timer (struct hrtimer *timer); +#endif /* _SCHED_DEADLINE_H */
kernel/sched/core.c +17 -4
@@ -1840,6 +1840,7 @@ static void __sched_fork(unsigned long clone_flags, struct task_struct *p)RB_CLEAR_NODE (&p->dl.rb_node);hrtimer_init (&p->dl.dl_timer,CLOCK_MONOTONIC ,HRTIMER_MODE_REL ); + p->dl.dl_timer.function = dl_task_timer;__dl_clear_params (p);INIT_LIST_HEAD (&p->rt.run_list);
@@ -3250,16 +3251,28 @@ static void __setparam_dl(struct task_struct *p, const struct sched_attr *attr) { struct sched_dl_entity *dl_se = &p->dl; +struct hrtimer *timer = &dl_se->dl_timer; +struct rq *rq =task_rq (p); + +/* +* There may be a race with dl_task_timer. If it's pending and +* we've failed to cancel it, timer's handler will unthrottle +* the task right after p's rq is unlocked. +*/ +if (!hrtimer_active (timer) ||hrtimer_try_to_cancel (timer) !=-1 ) { + dl_se->dl_throttled =0 ; + dl_se->dl_new =1 ; + dl_se->dl_yielded =0 ; + }else { + dl_se->deadline =rq_clock (rq) + attr->sched_deadline; + dl_se->runtime = attr->sched_runtime; + } -init_dl_task_timer (dl_se); dl_se->dl_runtime = attr->sched_runtime; dl_se->dl_deadline = attr->sched_deadline; dl_se->dl_period = attr->sched_period ?: dl_se->dl_deadline; dl_se->flags = attr->sched_flags; dl_se->dl_bw =to_ratio (dl_se->dl_period, dl_se->dl_runtime); - dl_se->dl_throttled =0 ; - dl_se->dl_new =1 ; - dl_se->dl_yielded =0 ; }/*
kernel/sched/deadline.c +1 -9
@@ -500,7 +500,7 @@ static int start_dl_timer(struct sched_dl_entity *dl_se, bool boosted)* updating (and the queueing back to dl_rq) will be done by the * next call to enqueue_task_dl(). */ -static enum hrtimer_restart dl_task_timer (struct hrtimer *timer) +enum hrtimer_restart dl_task_timer (struct hrtimer *timer) { struct sched_dl_entity *dl_se = container_of(timer, struct sched_dl_entity,
@@ -559,14 +559,6 @@ static enum hrtimer_restart dl_task_timer(struct hrtimer *timer)return HRTIMER_NORESTART ; } -void init_dl_task_timer (struct sched_dl_entity *dl_se) -{ -struct hrtimer *timer = &dl_se->dl_timer; - -hrtimer_init (timer,CLOCK_MONOTONIC ,HRTIMER_MODE_REL ); - timer->function = dl_task_timer; -} -static int dl_runtime_exceeded (struct rq *rq,struct sched_dl_entity *dl_se) {
kernel/sched/sched.h +0 -1
@@ -1257,7 +1257,6 @@ extern void init_rt_bandwidth(struct rt_bandwidth *rt_b, u64 period, u64 runtimeextern struct dl_bandwidth def_dl_bandwidth;extern void init_dl_bandwidth (struct dl_bandwidth *dl_b,u64 period,u64 runtime); -extern void init_dl_task_timer (struct sched_dl_entity *dl_se);unsigned long to_ratio (u64 period,u64 runtime);