sched/deadline: Fixes for constrained deadline tasks
PATCH v3 1/3 sched/deadline: Replenishment timer should fire in the next period
In reply to Daniel Bristot de OliveiraCurrently, the replenishment timer is set to fire at the deadline of a task. Although that works for implicit deadline tasks because the deadline is equals to the begin of the next period, that is not correct for constrained deadline tasks (deadline < period).
For instance:
f.c:
--------------- %< ---------------
int main (void)
{
for(;;);
}--------------- >% ---------------
# gcc -o f f.c
# trace-cmd record -e sched:sched_switch \
-e syscalls:sys_exit_sched_setattr \
chrt -d --sched-runtime 490000000 \
--sched-deadline 500000000 \
--sched-period 1000000000 0 ./f # trace-cmd report | grep "{pid of ./f}"After setting parameters, the task is replenished and continue running until being throttled.
f-11295 [003] 13322.113776: sys_exit_sched_setattr: 0x0
The task is throttled after running 492318 ms, as expected.
f-11295 [003] 13322.606094: sched_switch: f:11295 [-1] R ==> \ watchdog/3:32 [0]
But then, the task is replenished 500719 ms after the first replenishment:
<idle>-0 [003] 13322.614495: sched_switch: swapper/3:0 [120] R \ ==> f:11295 [-1]
Running for 490277 ms:
f-11295 [003] 13323.104772: sched_switch: f:11295 [-1] R ==> \ swapper/3:0 [120]
Hence, in the first period, the task runs 2 * runtime, and that is a bug.
During the first replenishment, the next deadline is set one period away. So the runtime / period starts to be respected. However, as the second replenishment took place in the wrong instant, the next replenishment will also be held in a wrong instant of time. Rather than occurring in the nth period away from the first activation, it is taking place in the (nth period - relative deadline).
--- kernel/sched/deadline.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
kernel/sched/deadline.c +7 -2
@@ -505,10 +505,15 @@ static void update_dl_entity(struct sched_dl_entity *dl_se, } } +static inline u64 dl_next_period (struct sched_dl_entity *dl_se) +{ +return dl_se->deadline - dl_se->dl_deadline + dl_se->dl_period; +} +/* * If the entity depleted all its runtime, and if we want it to sleep * while waiting for some new execution time to become available, we -* set the bandwidth enforcement timer to the replenishment instant +* set the bandwidth replenishment timer to the replenishment instant * and try to activate it. * * Notice that it is important for the caller to know if the timer
@@ -530,7 +535,7 @@ static int start_dl_timer(struct task_struct *p)* that it is actually coming from rq->clock and not from * hrtimer's time base reading. */ - act =ns_to_ktime (dl_se->deadline); + act =ns_to_ktime (dl_next_period (dl_se)); now =hrtimer_cb_get_time (timer); delta =ktime_to_ns (now) -rq_clock (rq); act =ktime_add_ns (act, delta);
Signature · 1 line
2.9.3
PATCH v3 3/3 sched/deadline: Use deadline instead of period when calculating overflow
In reply to Daniel Bristot de OliveiraFrom: "Steven Rostedt (VMware)" <[email protected]>
I was testing Daniel's changes with his test case, and tweaked it a little. Instead of having the runtime equal to the deadline, I increased the deadline ten fold.
Daniel's test case had:
attr.sched_runtime = 2 * 1000 * 1000; /* 2 ms */ attr.sched_deadline = 2 * 1000 * 1000; /* 2 ms */ attr.sched_period = 2 * 1000 * 1000 * 1000; /* 2 s */
To make it more interesting, I changed it to:
attr.sched_runtime = 2 * 1000 * 1000; /* 2 ms */ attr.sched_deadline = 20 * 1000 * 1000; /* 20 ms */ attr.sched_period = 2 * 1000 * 1000 * 1000; /* 2 s */
The results were rather surprising. The behavior that Daniel's patch was fixing came back. The task started using much more than .1% of the CPU. More like 20%.
Looking into this I found that it was due to the dl_entity_overflow() constantly returning true. That's because it uses the relative period against relative runtime vs the absolute deadline against absolute runtime.
runtime / (deadline - t) > dl_runtime / dl_period
There's even a comment mentioning this, and saying that when relative deadline equals relative period, that the equation is the same as using deadline instead of period. That comment is backwards! What we really want is:
runtime / (deadline - t) > dl_runtime / dl_deadline
We care about if the runtime can make its deadline, not its period. And then we can say "when the deadline equals the period, the equation is the same as using dl_period instead of dl_deadline".
After correcting this, now when the task gets enqueued, it can throttle correctly, and Daniel's fix to the throttling of sleeping deadline tasks works even when the runtime and deadline are not the same.
--- kernel/sched/deadline.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
kernel/sched/deadline.c +4 -4
@@ -445,13 +445,13 @@ static void replenish_dl_entity(struct sched_dl_entity *dl_se,* * This function returns true if: * -* runtime / (deadline - t) > dl_runtime / dl_period , +* runtime / (deadline - t) > dl_runtime / dl_deadline , * * IOW we can't recycle current parameters. * -* Notice that the bandwidth check is done against the period. For +* Notice that the bandwidth check is done against the deadline. For * task with deadline equal to period this is the same of using -* dl_deadline instead of dl_period in the equation above. +* dl_period instead of dl_deadline in the equation above. */ static bool dl_entity_overflow (struct sched_dl_entity *dl_se,struct sched_dl_entity *pi_se,u64 t)
@@ -476,7 +476,7 @@ static bool dl_entity_overflow(struct sched_dl_entity *dl_se,* of anything below microseconds resolution is actually fiction * (but still we want to give the user that illusion >;). */ - left = (pi_se->dl_period >>DL_SCALE ) * (dl_se->runtime >>DL_SCALE ); + left = (pi_se->dl_deadline >>DL_SCALE ) * (dl_se->runtime >>DL_SCALE ); right = ((dl_se->deadline - t) >>DL_SCALE ) * (pi_se->dl_runtime >>DL_SCALE );
Signature · 1 line
2.9.3
v3 cover sched/deadline: Fixes for constrained deadline tasks
While reading sched deadline code, I find out that a constrained deadline task could be replenished before the next period if activated after the deadline, opening the window to run for more than Q/P. The patch [2] explains and fixes this problem.
Furthermore, while fixing this issue, I found that the replenishment timer was being fired at the deadline of the task. This works fine for implicit deadline tasks (deadline == period) because the deadline is at the same point in time of the next period. But that is not true for constrained deadline tasks (deadline < period). This problem is not as visible as the first because the runtime leakage takes place only in the second activation. Next activations receive the correct bandwidth. However, after the 2nd activation, tasks are activated in the (period - dl_deadline) instant, which is before the expected activation. This problem is explained in the fix description as well.
While testing these fixes, Rostedt tweaked the test case a little. Instead of having the runtime equal to the deadline, he increased the deadline ten fold. Then, the task started using much more than .1% of the CPU. More like 20%. Looking into this he found that it was due to the dl_entity_overflow() constantly returning true. That's because it uses the relative period against relative runtime vs the absolute deadline against absolute runtime. As we care about if the runtime can make its deadline, not its period, we need to use the task's density in the check, not the task's utilization. After correcting this, now when the task gets enqueued, it can throttle correctly.
Changes from V2:
- Fixes dl_entity_overflow(): (Steven Rostedt)
Patch 3/3 fixes the dl_entity_overflow() for constrained deadline tasks by using the density, not the utilization.
(as deadline <= period, deadline is always == min(deadline, period))
Changes from V1:
- Fix a broken comment style. (Peter Zijlstra)
- Fixes dl_is_constrained(). (Steven Rostedt)
A constrained deadline task has dl_deadline < dl_period; so
"dl_runtime < dl_period"; s/runtime/deadline/
Daniel Bristot de Oliveira (2):
sched/deadline: Replenishment timer should fire in the next period sched/deadline: Throttle a constrained deadline task activated after
the deadline
Steven Rostedt (VMware) (1):
sched/deadline: Use deadline instead of period when calculating
overflow
kernel/sched/deadline.c | 62 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 6 deletions(-)
Signature · 1 line
2.9.3
PATCH v3 2/3 sched/deadline: Throttle a constrained deadline task activated after the deadline
During the activation, CBS checks if it can reuse the current task's runtime and period. If the deadline of the task is in the past, CBS cannot use the runtime, and so it replenishes the task. This rule works fine for implicit deadline tasks (deadline == period), and the CBS was designed for implicit deadline tasks. However, a task with constrained deadline (deadine < period) might be awakened after the deadline, but before the next period. In this case, replenishing the task would allow it to run for runtime / deadline. As in this case deadline < period, CBS enables a task to run for more than the runtime / period. In a very load system, this can cause the domino effect, making other tasks to miss their deadlines.
To avoid this problem, in the activation of a constrained deadline task after the deadline but before the next period, throttle the task and set the replenishing timer to the begin of the next period, unless it is boosted.
Reproducer:
--------------- %< ---------------
int main (int argc, char **argv)
{
int ret;
int flags = 0;
unsigned long l = 0;
struct timespec ts;
struct sched_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.sched_policy = SCHED_DEADLINE;
attr.sched_runtime = 2 * 1000 * 1000; /* 2 ms */
attr.sched_deadline = 2 * 1000 * 1000; /* 2 ms */
attr.sched_period = 2 * 1000 * 1000 * 1000; /* 2 s */
ts.tv_sec = 0;
ts.tv_nsec = 2000 * 1000; /* 2 ms */
ret = sched_setattr(0, &attr, flags);
if (ret < 0) {
perror("sched_setattr");
exit(-1);
}
for(;;) {
/* XXX: you may need to adjust the loop */
for (l = 0; l < 150000; l++);
/*
* The ideia is to go to sleep right before the deadline
* and then wake up before the next period to receive
* a new replenishment.
*/
nanosleep(&ts, NULL);
}
exit(0); }
--------------- >% ---------------
On my box, this reproducer uses almost 50% of the CPU time, which is obviously wrong for a task with 2/2000 reservation.
--- kernel/sched/deadline.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+)
kernel/sched/deadline.c +45 -0
@@ -694,6 +694,37 @@ void init_dl_task_timer(struct sched_dl_entity *dl_se) timer->function = dl_task_timer; } +/* +* During the activation, CBS checks if it can reuse the current task's +* runtime and period. If the deadline of the task is in the past, CBS +* cannot use the runtime, and so it replenishes the task. This rule +* works fine for implicit deadline tasks (deadline == period), and the +* CBS was designed for implicit deadline tasks. However, a task with +* constrained deadline (deadine < period) might be awakened after the +* deadline, but before the next period. In this case, replenishing the +* task would allow it to run for runtime / deadline. As in this case +* deadline < period, CBS enables a task to run for more than the +* runtime / period. In a very load system, this can cause the domino +* effect, making other tasks to miss their deadlines. +* +* To avoid this problem, in the activation of a constrained deadline +* task after the deadline but before the next period, throttle the +* task and set the replenishing timer to the begin of the next period, +* unless it is boosted. +*/ +static inline void dl_check_constrained_dl (struct sched_dl_entity *dl_se) +{ +struct task_struct *p =dl_task_of (dl_se); +struct rq *rq =rq_of_dl_rq (dl_rq_of_se (dl_se)); + +if (dl_time_before (dl_se->deadline,rq_clock (rq)) && +dl_time_before (rq_clock (rq),dl_next_period (dl_se))) { +if (unlikely (dl_se->dl_boosted || !start_dl_timer (p))) +return ; + dl_se->dl_throttled =1 ; + } +} +static int dl_runtime_exceeded (struct sched_dl_entity *dl_se) {
@@ -927,6 +958,11 @@ static void dequeue_dl_entity(struct sched_dl_entity *dl_se)__dequeue_dl_entity (dl_se); } +static inline bool dl_is_constrained (struct sched_dl_entity *dl_se) +{ +return dl_se->dl_deadline < dl_se->dl_period; +} +static void enqueue_task_dl (struct rq *rq,struct task_struct *p,int flags) {struct task_struct *pi_task =rt_mutex_get_top_task (p);
@@ -953,6 +989,15 @@ static void enqueue_task_dl(struct rq *rq, struct task_struct *p, int flags) }/* +* Check if a constrained deadline task was activated +* after the deadline but before the next period. +* If that is the case, the task will be throttled and +* the replenishment timer will be set to the next period. +*/ +if (!pi_se->dl_throttled &&dl_is_constrained (pi_se)) +dl_check_constrained_dl (pi_se); + +/* * If p is throttled, we do nothing. In fact, if it exhausted * its budget it needs a replenishment and, since it now is on * its rq, the bandwidth timer callback (which clearly has not
Signature · 1 line
2.9.3
Re: [PATCH V3 2/3] sched/deadline: Throttle a constrained deadline task activated after the deadline
Quoted 9 lines · On Tue, 28 Feb 2017 11:07:54 +0100 Daniel Bristot de Oliveira…
kernel/sched/deadline.c +1
-0@@ -694,6 +694,37 @@ void init_dl_task_timer(struct sched_dl_entity *dl_se) timer->function = dl_task_timer; } +/*
kernel/sched/deadline.c void init_dl_task_timer(struct sched_dl_entity *dl_se) · line 707
Quoted 10 lines
kernel/sched/deadline.c +10
-0+* During the activation, CBS checks if it can reuse the current task's +* runtime and period. If the deadline of the task is in the past, CBS +* cannot use the runtime, and so it replenishes the task. This rule +* works fine for implicit deadline tasks (deadline == period), and the +* CBS was designed for implicit deadline tasks. However, a task with +* constrained deadline (deadine < period) might be awakened after the +* deadline, but before the next period. In this case, replenishing the +* task would allow it to run for runtime / deadline. As in this case +* deadline < period, CBS enables a task to run for more than the +* runtime / period. In a very load system, this can cause the domino
In a very loaded system, this can cause a domino
kernel/sched/deadline.c void init_dl_task_timer(struct sched_dl_entity *dl_se) · line 708
Quoted 1 line
kernel/sched/deadline.c +1
-0+* effect, making other tasks to miss their deadlines.
effect, making other tasks miss their deadlines.
kernel/sched/deadline.c void init_dl_task_timer(struct sched_dl_entity *dl_se) · line 712
Quoted 4 lines
kernel/sched/deadline.c +4
-0+* +* To avoid this problem, in the activation of a constrained deadline +* task after the deadline but before the next period, throttle the +* task and set the replenishing timer to the begin of the next period,
to the beginning of the next
Quoted 36 lines
kernel/sched/deadline.c +22
-0+* unless it is boosted. +*/ +static inline void dl_check_constrained_dl (struct sched_dl_entity *dl_se) +{ +struct task_struct *p =dl_task_of (dl_se); +struct rq *rq =rq_of_dl_rq (dl_rq_of_se (dl_se)); + +if (dl_time_before (dl_se->deadline,rq_clock (rq)) && +dl_time_before (rq_clock (rq),dl_next_period (dl_se))) { +if (unlikely (dl_se->dl_boosted || !start_dl_timer (p))) +return ; + dl_se->dl_throttled =1 ; + } +} +static int dl_runtime_exceeded (struct sched_dl_entity *dl_se) {@@ -927,6 +958,11 @@ static void dequeue_dl_entity(struct sched_dl_entity *dl_se)__dequeue_dl_entity (dl_se); } +static inline bool dl_is_constrained (struct sched_dl_entity *dl_se) +{ +return dl_se->dl_deadline < dl_se->dl_period; +} +static void enqueue_task_dl (struct rq *rq,struct task_struct *p,int flags) {struct task_struct *pi_task =rt_mutex_get_top_task (p);@@ -953,6 +989,15 @@ static void enqueue_task_dl(struct rq *rq, struct task_struct *p, int flags) }/* +* Check if a constrained deadline task was activated +* after the deadline but before the next period.
kernel/sched/deadline.c static void enqueue_task_dl(struct rq *rq, struct task_struct *p, int flags) · line 1003
Quoted 10 lines
kernel/sched/deadline.c +7
-0+* If that is the case, the task will be throttled and +* the replenishment timer will be set to the next period. +*/ +if (!pi_se->dl_throttled &&dl_is_constrained (pi_se)) +dl_check_constrained_dl (pi_se); + +/* * If p is throttled, we do nothing. In fact, if it exhausted * its budget it needs a replenishment and, since it now is on * its rq, the bandwidth timer callback (which clearly has not
Except for the grammar mistakes...
-- Steve
Re: [PATCH V3 2/3] sched/deadline: Throttle a constrained deadline task activated after the deadline
In reply to Daniel Bristot de Oliveirakernel/sched/deadline.c static void enqueue_task_dl(struct rq *rq, struct task_struct *p, int flags) · line 999
Quoted 3 lines · Daniel Bristot de Oliveira
kernel/sched/deadline.c +3
-0+if (!pi_se->dl_throttled &&dl_is_constrained (pi_se)) +dl_check_constrained_dl (pi_se); +
me--, it should be &p->dl, not pi_se. This is not causing problems in the test case because pi_se = &p->dl, but this would be a problem if we were activating the task in a PI case: it would check the pi-waiter, not the task being awakened (p).
In the dl_check_constrained_dl(), the patch is already avoiding throttling if the task is "dl_boosted." So it is ready to avoid throttling the task in the PI control.
Cooking a v4, sorry for the noise.
-- Daniel
Re: [PATCH V3 1/3] sched/deadline: Replenishment timer should fire in the next period
In reply to Daniel Bristot de Oliveira2017-02-28 18:07 GMT+08:00 Daniel Bristot de Oliveira <[email protected]>:
Quoted 23 lines
Currently, the replenishment timer is set to fire at the deadline of a task. Although that works for implicit deadline tasks because the deadline is equals to the begin of the next period, that is not correct for constrained deadline tasks (deadline < period).
For instance:
f.c:
--------------- %< ---------------
int main (void){ for(;;); }--------------- >% ---------------
# gcc -o f f.c# trace-cmd record -e sched:sched_switch \ -e syscalls:sys_exit_sched_setattr \ chrt -d --sched-runtime 490000000 \ --sched-deadline 500000000 \ --sched-period 1000000000 0 ./f
Quoted 7 lines
# trace-cmd report | grep "{pid of ./f}"After setting parameters, the task is replenished and continue running until being throttled.
f-11295 [003] 13322.113776: sys_exit_sched_setattr: 0x0The task is throttled after running 492318 ms, as expected.
This should be us.
Otherwise,
Quoted 72 lines
f-11295 [003] 13322.606094: sched_switch: f:11295 [-1] R ==> \ watchdog/3:32 [0]But then, the task is replenished 500719 ms after the first replenishment:
<idle>-0 [003] 13322.614495: sched_switch: swapper/3:0 [120] R \ ==> f:11295 [-1]Running for 490277 ms:
f-11295 [003] 13323.104772: sched_switch: f:11295 [-1] R ==> \ swapper/3:0 [120]Hence, in the first period, the task runs 2 * runtime, and that is a bug.
During the first replenishment, the next deadline is set one period away. So the runtime / period starts to be respected. However, as the second replenishment took place in the wrong instant, the next replenishment will also be held in a wrong instant of time. Rather than occurring in the nth period away from the first activation, it is taking place in the (nth period - relative deadline).
Signed-off-by Daniel Bristot de Oliveira <[email protected]>Reviewed-by Luca Abeni <[email protected]>Reviewed-by Steven Rostedt (VMware) <[email protected]>Reviewed-by Juri Lelli <[email protected]>Cc Ingo Molnar <[email protected]>Cc Peter Zijlstra <[email protected]>Cc Juri Lelli <[email protected]>Cc Tommaso Cucinotta <[email protected]>Cc Luca Abeni <[email protected]>Cc Steven Rostedt <[email protected]>Cc Mike Galbraith <[email protected]>Cc Romulo Silva de Oliveira <[email protected]>Cc Daniel Bristot de Oliveira <[email protected]>--- kernel/sched/deadline.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)kernel/sched/deadline.c +7
-3@@ -505,10 +505,15 @@ static void update_dl_entity(struct sched_dl_entity *dl_se, } } +static inline u64 dl_next_period (struct sched_dl_entity *dl_se) +{ +return dl_se->deadline - dl_se->dl_deadline + dl_se->dl_period; +} +/* * If the entity depleted all its runtime, and if we want it to sleep * while waiting for some new execution time to become available, we -* set the bandwidth enforcement timer to the replenishment instant +* set the bandwidth replenishment timer to the replenishment instant * and try to activate it. * * Notice that it is important for the caller to know if the timer @@ -530,7 +535,7 @@ static int start_dl_timer(struct task_struct *p)* that it is actually coming from rq->clock and not from * hrtimer's time base reading. */ - act =ns_to_ktime (dl_se->deadline); + act =ns_to_ktime (dl_next_period (dl_se)); now =hrtimer_cb_get_time (timer); delta =ktime_to_ns (now) -rq_clock (rq); act =ktime_add_ns (act, delta); --2.9.3