[PATCH 01/20 v3] sched/eevdf: Decay positive lag of sleeping entities
From: Vincent Guittot
Date: Fri Nov 06 2026 - 10:25:30 EST
Similarly to delayed dequeue, which enables an entity to decay its negative
lag while sleeping, a task should not keep a positive lag forever.
The sleep duration and the weight are used to decay the positive lag at
wakeup.
Apply the decay when enqueueing on prev rq for which we hold the lock to
get rq_clock_task().
Signed-off-by: Vincent Guittot <vincent.guittot@xxxxxxxxxx>
---
kernel/sched/fair.c | 124 ++++++++++++++++++++++++++++++++------------
1 file changed, 90 insertions(+), 34 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 0feec172fd37..5cd171f830d8 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -897,6 +897,91 @@ bool update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se)
return avruntime - vlag != se->vruntime;
}
+/*
+ * Signed add and clamp on underflow.
+ *
+ * Explicitly do a load-store to ensure the intermediate value never hits
+ * memory. This allows lockless observations without ever seeing the negative
+ * values.
+ */
+#define add_positive(_ptr, _val) do { \
+ typeof(_ptr) ptr = (_ptr); \
+ __signed_scalar_typeof(*ptr) val = (_val); \
+ typeof(*ptr) res, var = READ_ONCE(*ptr); \
+ \
+ res = var + val; \
+ \
+ if (val < 0 && res > var) \
+ res = 0; \
+ \
+ WRITE_ONCE(*ptr, res); \
+} while (0)
+
+/*
+ * Remove and clamp on negative, from a local variable.
+ *
+ * A variant of sub_positive(), which does not use explicit load-store
+ * and is thus optimized for local variable updates.
+ */
+#define lsub_positive(_ptr, _val) do { \
+ typeof(_ptr) ptr = (_ptr); \
+ *ptr -= min_t(typeof(*ptr), *ptr, _val); \
+} while (0)
+
+static inline unsigned long cfs_rq_load_avg(struct cfs_rq *cfs_rq);
+static unsigned long task_h_load(struct task_struct *p);
+
+static __always_inline
+void decay_entity_lag(struct cfs_rq *cfs_rq, struct task_struct *p, int flags)
+{
+ struct sched_entity *se = &p->se;
+ s64 delta_exec, vlag = se->vlag;
+ unsigned long cfs_load;
+ struct rq *rq;
+
+ WARN_ON_ONCE(se->on_rq);
+
+ /* Negative lag is managed by delayed dequeue path */
+ if (vlag <= 0)
+ return;
+
+ rq = rq_of(cfs_rq);
+
+ /* No fast access to prev rq task clock */
+ if (flags & ENQUEUE_MIGRATED)
+ return;
+
+ /* Compute sleep time */
+ delta_exec = rq_clock_task(rq) - se->exec_start;
+ if (unlikely(delta_exec <= 0))
+ return;
+
+ /* For anything above ~4 seconds, save computation and clear the lag */
+ if (unlikely(delta_exec >> 32)) {
+ se->vlag = 0;
+ return;
+ }
+
+ /*
+ * Use cfs' load and p's weight to estimate what would have been the
+ * share for p. task's load contribution to cfs' load is replaced by
+ * its weight.
+ */
+ cfs_load = cfs_rq_load_avg(cfs_rq);
+ lsub_positive(&cfs_load, task_h_load(p));
+ if (cfs_load) {
+ unsigned long weight = scale_load_down(se->h_load.weight);
+
+ delta_exec *= weight;
+ delta_exec = div64_long(delta_exec, cfs_load + weight);
+ }
+
+ vlag -= calc_delta_fair(delta_exec, se);
+
+ /* vlag can't become neg while sleeping */
+ se->vlag = max(0, vlag);
+}
+
/*
* Entity is eligible once it received less service than it ought to have,
* eg. lag >= 0.
@@ -4693,38 +4778,6 @@ account_entity_dequeue(struct cfs_rq *cfs_rq, struct sched_entity *se)
cfs_rq->nr_queued--;
}
-/*
- * Signed add and clamp on underflow.
- *
- * Explicitly do a load-store to ensure the intermediate value never hits
- * memory. This allows lockless observations without ever seeing the negative
- * values.
- */
-#define add_positive(_ptr, _val) do { \
- typeof(_ptr) ptr = (_ptr); \
- __signed_scalar_typeof(*ptr) val = (_val); \
- typeof(*ptr) res, var = READ_ONCE(*ptr); \
- \
- res = var + val; \
- \
- if (val < 0 && res > var) \
- res = 0; \
- \
- WRITE_ONCE(*ptr, res); \
-} while (0)
-
-/*
- * Remove and clamp on negative, from a local variable.
- *
- * A variant of sub_positive(), which does not use explicit load-store
- * and is thus optimized for local variable updates.
- */
-#define lsub_positive(_ptr, _val) do { \
- typeof(_ptr) ptr = (_ptr); \
- *ptr -= min_t(typeof(*ptr), *ptr, _val); \
-} while (0)
-
-
/*
* Because of rounding, se->util_sum might ends up being +1 more than
* cfs->util_sum. Although this is not a problem by itself, detaching
@@ -8152,7 +8205,7 @@ static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
int rq_h_nr_queued = rq->cfs.h_nr_queued;
- int task_new = !(flags & ENQUEUE_WAKEUP);
+ int task_wake = flags & ENQUEUE_WAKEUP;
struct sched_entity *se = &p->se;
struct cfs_rq *cfs_rq = &rq->cfs;
unsigned long weight;
@@ -8185,6 +8238,9 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
if (p->in_iowait)
cpufreq_update_util(rq, SCHED_CPUFREQ_IOWAIT);
+ if (task_wake)
+ decay_entity_lag(cfs_rq, p, flags);
+
curr = (cfs_rq->curr == se);
/*
* This can actually happen with CFS_BANDWIDTH, a task can be throttled
@@ -8225,7 +8281,7 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
* into account, but that is not straightforward to implement,
* and the following generally works well enough in practice.
*/
- if (!task_new)
+ if (task_wake)
check_update_overutilized_status(rq);
assert_list_leaf_cfs_rq(rq);
--
2.53.0