---
# System prepended metadata

title: 'Linux 核心設計: Scheduler(5): EEVDF 排程器/2'
tags: [Linux Kernel Internals, Scheduler, ' 作業系統']

---

# Linux 核心設計: Scheduler(5): EEVDF 排程器/2

## RFC Patches 2

前一章所研讀的 patches 最終並未正式合併到 kernel 中。維護者 Peter Zijlstra 針對此做了修正與調整，重新釋出了進階的改動 [[RFC][PATCH 00/10] sched/fair: Complete EEVDF](https://lore.kernel.org/lkml/20240405102754.435410987@infradead.org/?fbclid=IwAR22R73s3oT78BcbvQjjgKLJJPUqjS2YRsESUY3f67CmTsZPj6EXCV-Omzs)。在此系列內容中，除了原本對 time slice/request size 的支援，Peter 還新增 delayed-dequeue 機制以彌補原始論文在 EEVDF 上的缺陷。這些改動將成為補足 EEVDF 的最後一塊拼圖。而本節將針對這一系列內容進行探討。

### [RFC][PATCH 01/10] sched/eevdf: Add feature comments

單純是加上註解描述幾個 SCHED_FEAT。

```diff
--- a/kernel/sched/features.h
+++ b/kernel/sched/features.h
@@ -5,7 +5,14 @@
  * sleep+wake cycles. EEVDF placement strategy #1, #2 if disabled.
  */
 SCHED_FEAT(PLACE_LAG, true)
+/*
+ * Give new tasks half a slice to ease into the competition.
+ */
 SCHED_FEAT(PLACE_DEADLINE_INITIAL, true)
+/*
+ * Inhibit (wakeup) preemption until the current task has either matched the
+ * 0-lag point or until is has exhausted it's slice.
+ */
 SCHED_FEAT(RUN_TO_PARITY, true)
```

之前的內容已經對這些 feature 做了介紹。這裡就簡單再總結一下:
* `PLACE_DEADLINE_INITIAL`: 將新建立的任務 deadline 刻意提早，以獲得更早被排程的機會
* `RUN_TO_PARITY`: 目的是減少激進的搶占。一般來說，應該是總要選擇 deadline 最小的任務優先執行。但此 feature 會讓當下選取的任務一直被執行到 non-eligible 或者取得一個新的 slice，期間內不會被其他任務搶占。

### [RFC][PATCH 02/10] sched/eevdf: Remove min_vruntime_copy

過去存在名為 `min_vruntime_copy` 的變數，它與不同 CPU 之間的同步相關，具體使用到它的程式碼大致如下(參照 [5.19.17 版的 fair.c](https://elixir.bootlin.com/linux/v5.19.17/source/kernel/sched/fair.c))。

```cpp
void init_cfs_rq(struct cfs_rq *cfs_rq)
{
	...
	cfs_rq->min_vruntime = (u64)(-(1LL << 20));
#ifndef CONFIG_64BIT
	cfs_rq->min_vruntime_copy = cfs_rq->min_vruntime;
#endif
}

static void update_min_vruntime(struct cfs_rq *cfs_rq)
{
	...
	/* ensure we never gain time by being placed backwards. */
	cfs_rq->min_vruntime = max_vruntime(cfs_rq->min_vruntime, vruntime);
#ifndef CONFIG_64BIT
	smp_wmb();
	cfs_rq->min_vruntime_copy = cfs_rq->min_vruntime;
#endif
}

static void migrate_task_rq_fair(struct task_struct *p, int new_cpu)
{
	if (READ_ONCE(p->__state) == TASK_WAKING) {
		struct sched_entity *se = &p->se;
		struct cfs_rq *cfs_rq = cfs_rq_of(se);
		u64 min_vruntime;

#ifndef CONFIG_64BIT
		u64 min_vruntime_copy;

		do {
			min_vruntime_copy = cfs_rq->min_vruntime_copy;
			smp_rmb();
			min_vruntime = cfs_rq->min_vruntime;
		} while (min_vruntime != min_vruntime_copy);
#else
		min_vruntime = cfs_rq->min_vruntime;
#endif
		...
```

在 `CONFIG_64BIT` 沒有被設置的狀況下，表示 Linux 是使用在 32 位元的機器上。則此時存取存 64 位元的整數時，CPU 需要兩次的記憶體操作才能完成，換句話說並非 atomic 操作。因此，為確保在 reader 端得到的 `min_vruntime` 可以正確反映 writer 端的更新，`min_vruntime_copy` 搭配 read/write barrier 可以做為同步機制來避免上述的問題。這個技巧與 [sequential locks](https://docs.kernel.org/locking/seqlock.html) 有異曲同工之妙。具體的改動來自 [[PATCH 10/22] sched: Deal with non-atomic min_vruntime reads on 32bits](https://lore.kernel.org/lkml/20110302174120.976363449@chello.nl/)。之後，考慮到其他變數也需要類似技巧，後來一系列操作又以 `u64_u32_store` macro 被提供。

在 [[PATCH 07/15] sched/smp: Use lag to simplify cross-runqueue placement](https://hackmd.io/1wvEMFFVQ5ui4iPd5fPfIg#PATCH-0715-schedsmp-Use-lag-to-simplify-cross-runqueue-placement) 之後，任務在不同 CPU 核心間轉移(migration)時，不再是用 `min_vruntime` 來標準化對應 vruntime。因此在此 patch 中也將相應內容移除。

```diff
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -780,8 +780,7 @@ static void update_min_vruntime(struct c
 	}
 
 	/* ensure we never gain time by being placed backwards. */
-	u64_u32_store(cfs_rq->min_vruntime,
-		      __update_min_vruntime(cfs_rq, vruntime));
+	cfs_rq->min_vruntime = __update_min_vruntime(cfs_rq, vruntime);
 }
 
 static inline bool __entity_less(struct rb_node *a, const struct rb_node *b)
@@ -12876,7 +12875,7 @@ static void set_next_task_fair(struct rq
 void init_cfs_rq(struct cfs_rq *cfs_rq)
 {
 	cfs_rq->tasks_timeline = RB_ROOT_CACHED;
-	u64_u32_store(cfs_rq->min_vruntime, (u64)(-(1LL << 20)));
+	cfs_rq->min_vruntime = (u64)(-(1LL << 20));
 #ifdef CONFIG_SMP
 	raw_spin_lock_init(&cfs_rq->removed.lock);
 #endif
```

```diff
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -599,10 +599,6 @@ struct cfs_rq {
 	u64			min_vruntime_fi;
 #endif
 
-#ifndef CONFIG_64BIT
-	u64			min_vruntime_copy;
-#endif
-
 	struct rb_root_cached	tasks_timeline;
 
 	/*
```

### [RFC][PATCH 03/10] sched/fair: Cleanup pick_task_fair() vs throttle

`check_cfs_rq_runtime` 與 bandwith throttling 有關。概括來說是用來限制一個任務在每個周期中可以消耗 CPU 的時間，而簡單總結其作法的話，是將符合受限條件的 schedule entity(se) 從排程 runqueue 中移除。

在 `pick_next_task_fair` 中，這段邏輯被放在 `if(curr)` 的條件式中。這是因為如果不在此前提下的話，throttling 的結果最終可能會導致 runqueue 為空，因此造成後續挑選任務的邏輯(`set_next_entity`)產生錯誤。詳見 [[PATCH] sched/fair: Prevent throttling in early pick_next_task_fair()](https://lore.kernel.org/stable/20151018014907.245719036@linuxfoundation.org/) 的敘述。

```diff
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -8435,11 +8435,11 @@ static struct task_struct *pick_task_fai
		if (curr) {
			if (curr->on_rq)
				update_curr(cfs_rq);
			else
				curr = NULL;
-
-			if (unlikely(check_cfs_rq_runtime(cfs_rq)))
-				goto again;
 		}
 
+		if (unlikely(check_cfs_rq_runtime(cfs_rq)))
+			goto again;
+
 		se = pick_next_entity(cfs_rq);
 		cfs_rq = group_cfs_rq(se);
 	} while (cfs_rq);
```

原本的 `pick_task_fair` 之邏輯是從  `pick_next_task_fair` 複製過來的(見 [[sched/core] sched: Introduce sched_class::pick_task](https://lore.kernel.org/lkml/162081530531.29796.2208363751943226264.tip-bot2@tip-bot2/))。但在 `pick_task_fair` 的 code path 上，throttle 任何任務皆不會造成上述的錯誤。因此此 patch 對此作出調整。

### [RFC][PATCH 04/10] sched/fair: Cleanup pick_task_fair()s curr

```diff
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -8427,15 +8427,9 @@ static struct task_struct *pick_task_fai
 		return NULL;
 
 	do {
-		struct sched_entity *curr = cfs_rq->curr;
-
 		/* When we pick for a remote RQ, we'll not have done put_prev_entity() */
-		if (curr) {
-			if (curr->on_rq)
-				update_curr(cfs_rq);
-			else
-				curr = NULL;
-		}
+		if (cfs_rq->curr && cfs_rq->curr->on_rq)
+			update_curr(cfs_rq);
 
 		if (unlikely(check_cfs_rq_runtime(cfs_rq)))
 			goto again;
```

結合前一個 patch 的改動，現在我們可以移除 `curr` 這個變數。

### [RFC][PATCH 05/10] sched/fair: Unify pick_{,next_}_task_fair()

經過上面的調整後，`pick_task_fair` 的邏輯完全對應到 `pick_next_task_fair` 中的一部分，因此就可以將兩者整合起來以避免重複的程式碼。

### [RFC][PATCH 06/10] sched: Allow sched_class::dequeue_task() to fail

原始的 `dequeue_task` 設計是沒有回傳值的，這意味著呼叫 dequeue 的結果無法被確認，例如是否有成功。由於 EEVDF 的新功能需要相關的資訊，因此在此 patch 中調整該 callback 的 signature。

```diff
 /*
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -2278,7 +2278,7 @@ struct sched_class {
 #endif
 
 	void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
-	void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
+	bool (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
 	void (*yield_task)   (struct rq *rq);
 	bool (*yield_to_task)(struct rq *rq, struct task_struct *p);
```

```diff
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -2119,7 +2119,10 @@ static inline void enqueue_task(struct r
 		sched_core_enqueue(rq, p);
 }
 
-static inline void dequeue_task(struct rq *rq, struct task_struct *p, int flags)
+/*
+ * Must only return false when DEQUEUE_SLEEP.
+ */
+static inline bool dequeue_task(struct rq *rq, struct task_struct *p, int flags)
 {
 	if (sched_core_enabled(rq))
 		sched_core_dequeue(rq, p, flags);
@@ -2133,7 +2136,7 @@ static inline void dequeue_task(struct r
 	}
 
 	uclamp_rq_dec(rq, p);
-	p->sched_class->dequeue_task(rq, p, flags);
+	return p->sched_class->dequeue_task(rq, p, flags);
 }
```

下面就是對 kernel 中所有類型的 scheduler，包含 deadline, idle, fair, rt, 和 stop 都做相對應的調整。

```diff
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -1842,7 +1842,7 @@ static void enqueue_task_dl(struct rq *r
 		enqueue_pushable_dl_task(rq, p);
 }
 
-static void dequeue_task_dl(struct rq *rq, struct task_struct *p, int flags)
+static bool dequeue_task_dl(struct rq *rq, struct task_struct *p, int flags)
 {
 	update_curr_dl(rq);
 
@@ -1852,6 +1852,8 @@ static void dequeue_task_dl(struct rq *r
 	dequeue_dl_entity(&p->dl, flags);
 	if (!p->dl.dl_throttled && !dl_server(&p->dl))
 		dequeue_pushable_dl_task(rq, p);
+
+	return true;
 }
```

```diff
 /*
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -6828,7 +6828,7 @@ static void set_next_buddy(struct sched_
  * decreased. We remove the task from the rbtree and
  * update the fair scheduling stats:
  */
-static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
+static bool dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
 {
 	struct cfs_rq *cfs_rq;
 	struct sched_entity *se = &p->se;
@@ -6896,6 +6896,8 @@ static void dequeue_task_fair(struct rq
 dequeue_throttle:
 	util_est_update(&rq->cfs, p, task_sleep);
 	hrtick_update(rq);
+
+	return true;
 }
```

```diff
 #ifdef CONFIG_SMP
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -486,13 +486,14 @@ struct task_struct *pick_next_task_idle(
  * It is not legal to sleep in the idle task - print a warning
  * message if some code attempts to do it:
  */
-static void
+static bool
 dequeue_task_idle(struct rq *rq, struct task_struct *p, int flags)
 {
 	raw_spin_rq_unlock_irq(rq);
 	printk(KERN_ERR "bad: scheduling from the idle thread!\n");
 	dump_stack();
 	raw_spin_rq_lock_irq(rq);
+	return true;
 }
```

```diff
 /*
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -1493,7 +1493,7 @@ enqueue_task_rt(struct rq *rq, struct ta
 		enqueue_pushable_task(rq, p);
 }
 
-static void dequeue_task_rt(struct rq *rq, struct task_struct *p, int flags)
+static bool dequeue_task_rt(struct rq *rq, struct task_struct *p, int flags)
 {
 	struct sched_rt_entity *rt_se = &p->rt;
 
@@ -1501,6 +1501,8 @@ static void dequeue_task_rt(struct rq *r
 	dequeue_rt_entity(rt_se, flags);
 
 	dequeue_pushable_task(rq, p);
+
+	return true;
 }
```

```diff
--- a/kernel/sched/stop_task.c
+++ b/kernel/sched/stop_task.c
@@ -57,10 +57,11 @@ enqueue_task_stop(struct rq *rq, struct
 	add_nr_running(rq, 1);
 }
 
-static void
+static bool
 dequeue_task_stop(struct rq *rq, struct task_struct *p, int flags)
 {
 	sub_nr_running(rq, 1);
+	return true;
 }
```

### [RFC][PATCH 07/10] sched/fair: Re-organize dequeue_task_fair()

結合上一項改動，這一系列的調整是為了 EEVDF 需要的 "delaying dequeue" 機制。本筆 commit 對 `dequeue_task_fair` 的實作方式進行了重構。

原本的 `dequeue_task_fair` 中的大部分內容被獨立為函式 `dequeue_entities()`，這是為了讓 `dequeue_entities()` 中的邏輯可以讓 `dequeue_task_fair` 以外的地方也可以共用。

關於 `dequeue_task_fair()` 的詳細分析可在 [`dequeue_task_fair`](https://hackmd.io/@RinHizakura/BJ9m_qs-5#dequeue_task_fair) 一章中參閱，以下僅針對改動前後需留意的差異進行說明。

```cpp
 /*
  * Basically dequeue_task_fair(), except it can deal with dequeue_entity()
 * failing half-way through and resume the dequeue later.
  *
  * Returns:
  * -1 - dequeue delayed
  *  0 - dequeue throttled
  *  1 - dequeue complete
  */
static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags)
 {
 	bool was_sched_idle = sched_idle_rq(rq);
	bool task_sleep = flags & DEQUEUE_SLEEP;
	struct task_struct *p = NULL;
	struct cfs_rq *cfs_rq;
	int idle_h_nr_running;
 
	if (entity_is_task(se)) {
		p = task_of(se);
		idle_h_nr_running = task_has_idle_policy(p);
	} else {
		idle_h_nr_running = cfs_rq_is_idle(group_cfs_rq(se));
	}
```

`idle_h_nr_running` 的獲得方式與原本在 `dequeue_task_fair` 中的做法不同。在原本實作中是直接透過 `task_has_idle_policy(p)` 取得，因為只要考慮 `task_struct` 的情況即可，而此處則針對 task group 的狀況也做考量。


```cpp
 	for_each_sched_entity(se) {
 		cfs_rq = cfs_rq_of(se);
 		dequeue_entity(cfs_rq, se, flags);
 
		/* h_nr_running is the hierachical count of tasks */
		if (p) {
			cfs_rq->h_nr_running--;
			cfs_rq->idle_h_nr_running -= idle_h_nr_running;
 
			if (cfs_rq_is_idle(cfs_rq))
				idle_h_nr_running = 1;
		}
 
 		/* end evaluation on encountering a throttled cfs_rq */
 		if (cfs_rq_throttled(cfs_rq))
			return 0;
 
 		/* Don't dequeue parent if it has other entities besides us */
 		if (cfs_rq->load.weight) {
			/* Avoid re-evaluating load for this entity: */
			se = parent_entity(se);
			/*
			 * Bias pick_next to pick a task from this cfs_rq, as
			 * p is sleeping when it is within its sched_slice.
			 */
			if (task_sleep && se && !throttled_hierarchy(cfs_rq))
				set_next_buddy(se);
			break;
		}
		flags |= DEQUEUE_SLEEP;
	}
```

在第一個 `for_each_sched_entity` 中，與原先的不同在於 `if(p){...}` 的部分。如果 `se` 非來自 `task_struct` 的話，不必去做對應 `cfs_rq` 相關成員(如 `h_nr_running`、`idle_h_nr_running`)的更新。

```cpp
 	for_each_sched_entity(se) {
 		cfs_rq = cfs_rq_of(se);
 
		// XXX avoid these load updates for delayed dequeues ?
 		update_load_avg(cfs_rq, se, UPDATE_TG);
 		se_update_runnable(se);
 		update_cfs_group(se);
 
		if (p) {
			cfs_rq->h_nr_running--;
			cfs_rq->idle_h_nr_running -= idle_h_nr_running;
 
			if (cfs_rq_is_idle(cfs_rq))
				idle_h_nr_running = 1;
		}
 
 		/* end evaluation on encountering a throttled cfs_rq */
 		if (cfs_rq_throttled(cfs_rq))
			return 0;
	}
```

第二個 `for_each_sched_entity()` 也是類似，避免一些 `cfs_rq` 中變數的更新。


```cpp
	if (p) {
		sub_nr_running(rq, 1);
 
		/* balance early to pull high priority tasks */
		if (unlikely(!was_sched_idle && sched_idle_rq(rq)))
			rq->next_balance = jiffies;
 	}
 
	return 1;
}
```

最後的部分，如果 `se` 非來自 `task_struct` 的話，有一些行為也直接略過。

```cpp
/*
 * The dequeue_task method is called before nr_running is
 * decreased. We remove the task from the rbtree and
 * update the fair scheduling stats:
 */
static bool dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
	util_est_dequeue(&rq->cfs, p);
 
	if (dequeue_entities(rq, &p->se, flags) < 0)
		return false;
 
	util_est_update(&rq->cfs, p, flags & DEQUEUE_SLEEP);
	hrtick_update(rq);
 	return true;
} 
```

綜合下來，`dequeue_task_fair` 與原始的行為並無二異，僅是改為利用了 `dequeue_entities`。

### [RFC][PATCH 08/10] sched/fair: Implement delayed dequeue

在 [sched/fair: Add lag based placement](https://hackmd.io/1wvEMFFVQ5ui4iPd5fPfIg#PATCH-0315-schedfair-Add-lag-based-placement) 一節，我們曾經討論到原始 EEVDF 論文在處理 lag 的缺陷。簡而言之，若 `se` 在退出排程時，其所剩餘之 lag 會在下次重新排程時繼承，則變相是在鼓勵任務在等待 CPU 時 spin wait 而不真正 sleep，以確保自己能夠得到足量的 CPU 資源。但反過來說，也不能完全不考量退出時的 lag，因為這反而導致任務刻意的 sleep 可以確保 CPU。

Peter Zijkstra 認為可以對應的辦法是稱為 delay dequeue 的機制。這種機制會把進入 sleep 但 lag 仍為負的 `se` 仍暫時保留在 queue 上。理想上，一直保留到 lag = 0 再將其 dequeued。不過實際上 kernel 不可能費心力追蹤該任務的 lag。取而代之，可以一直等到下次要選取該任務時，若其 eligible 才真正將其從 queue 中剃除。

理解了故事背景，再回頭看程式碼就很容易懂了。

```diff
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -542,6 +542,7 @@ struct sched_entity {
 
 	struct list_head		group_node;
 	unsigned int			on_rq;
+	unsigned int			sched_delayed;
 
 	u64				exec_start;
 	u64				sum_exec_runtime;
```


```diff
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -2154,10 +2154,14 @@ void activate_task(struct rq *rq, struct
 
 void deactivate_task(struct rq *rq, struct task_struct *p, int flags)
 {
-	WRITE_ONCE(p->on_rq, (flags & DEQUEUE_SLEEP) ? 0 : TASK_ON_RQ_MIGRATING);
-	ASSERT_EXCLUSIVE_WRITER(p->on_rq);
+	bool sleep = flags & DEQUEUE_SLEEP;
 
-	dequeue_task(rq, p, flags);
+	if (dequeue_task(rq, p, flags)) {
+		WRITE_ONCE(p->on_rq, sleep ? 0 : TASK_ON_RQ_MIGRATING);
+		ASSERT_EXCLUSIVE_WRITER(p->on_rq);
+	} else {
+		SCHED_WARN_ON(!sleep); /* only sleep can fail */
+	}
 }
```

前面的改動讓 `dequeue_task` 現在有了回傳值。若 dequeue 失敗，目前唯一的可能就是因為 sleep 導致的 delay dequeue。

```diff
 static inline int __normal_prio(int policy, int rt_prio, int nice)
@@ -3858,12 +3862,17 @@ static int ttwu_runnable(struct task_str
 
 	rq = __task_rq_lock(p, &rf);
 	if (task_on_rq_queued(p)) {
+		update_rq_clock(rq);
+		if (p->se.sched_delayed) {
+			/* mustn't run a delayed task */
+			SCHED_WARN_ON(task_on_cpu(rq, p));
+			enqueue_task(rq, p, ENQUEUE_DELAYED);
+		}
 		if (!task_on_cpu(rq, p)) {
 			/*
 			 * When on_rq && !on_cpu the task is preempted, see if
 			 * it should preempt the task that is current now.
 			 */
-			update_rq_clock(rq);
 			wakeup_preempt(rq, p, wake_flags);
 		}
 		ttwu_do_wakeup(p);
@@ -4243,11 +4252,16 @@ int try_to_wake_up(struct task_struct *p
 		 * case the whole 'p->on_rq && ttwu_runnable()' case below
 		 * without taking any locks.
 		 *
+		 * Specifically, given current runs ttwu() we must be before
+		 * schedule()'s deactivate_task(), as such this must not
+		 * observe sched_delayed.
+		 *
 		 * In particular:
 		 *  - we rely on Program-Order guarantees for all the ordering,
 		 *  - we're serialized against set_special_state() by virtue of
 		 *    it disabling IRQs (this allows not taking ->pi_lock).
 		 */
+		SCHED_WARN_ON(p->se.sched_delayed);
 		if (!ttwu_state_match(p, state, &success))
 			goto out;
```

`ttwu_runnable()` 函式的用途是作為 wakeup 的 fast path。如果試著 wakeup 某個任務時，該任務尚在 rq 中 runnable 還未被真正 dequeue，則直接將其狀態設為 `TASK_RUNNING` 就可完成 wakeup。

但在 delay dequeued 的狀況下，任務被留在 queue 中不一定是實際意義上的可以被排程。如果這個任務是因為 delay dequeue 而保留在 queue 中，我們需要 enqueue 這個被 delay 的任務，也就是通過 `ENQUEUE_DELAYED` flag 進行 `enqueue_task`，詳細流程會在後續說明。


```diff
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -5270,6 +5270,10 @@ static inline int cfs_rq_throttled(struc
 static inline bool cfs_bandwidth_used(void);
 
+ static void
+requeue_delayed_entity(struct sched_entity *se);

+/* XXX bool and pull in the requeue_delayed_entity thing */
static void
 enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
 {
 	bool curr = cfs_rq->curr == se;
@@ -5356,18 +5360,33 @@ static void clear_buddies(struct cfs_rq
 
 static __always_inline void return_cfs_rq_runtime(struct cfs_rq *cfs_rq);
 
-static void
+static bool
 dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
 {
 	/*
 	 * Update run-time statistics of the 'current'.
 	 */
+	if (flags & DEQUEUE_DELAYED) {
+		SCHED_WARN_ON(!se->sched_delayed);
+		se->sched_delayed = 0;
+	} else {
+		bool sleep = flags & DEQUEUE_SLEEP;
+
+		SCHED_WARN_ON(sleep && se->sched_delayed);
+		update_curr(cfs_rq);
+
+		if (sched_feat(DELAY_DEQUEUE) && sleep &&
+		    !entity_eligible(cfs_rq, se)) {
+			if (cfs_rq->next == se)
+				cfs_rq->next = NULL;
+			se->sched_delayed = 1;
+			return false;
+		}
+	}
+
	int action = UPDATE_TG;
	if (entity_is_task(se) && task_on_rq_migrating(task_of(se)))
		action |= DO_DETACH;

-	update_curr(cfs_rq);
 	/*
 	 * When dequeuing a sched_entity, we must:
@@ -5407,6 +5426,8 @@ dequeue_entity(struct cfs_rq *cfs_rq, st
 
 	if (cfs_rq->nr_running == 0)
 		update_idle_cfs_rq_clock_pelt(cfs_rq);
+
+	return true;
 }
```

這邊將 `dequeue_entity` 加上返回值，以傳遞 delay dequeue 發生的資訊給 `dequeue_task_fair`。

如果 flag 含 `DEQUEUE_DELAYED`，表示我們要 dequeue 的是一個被原本早該被 dequeue 但被 delay 的 se。這時就不需要對其 `update_curr()` 或判斷是否該 delay dequeue 等等，因為這是正常 `dequeue_entity`  流程的上半步驟，而該 se 已經完成這些，因此需要進行下半步驟就好。

如果 flag 不含 `DEQUEUE_DELAYED`，前面說到可以通過是否為 sleep 和 eligible 決定要否 delay dequeue 之。若條件滿足，該 se 在進行完 `dequeue_entity` 的上半步驟後直接 return false。否則就是正常執行完整的 dequeue。


```diff
 static void
@@ -5432,6 +5453,7 @@ set_next_entity(struct cfs_rq *cfs_rq, s
 	}
 
 	update_stats_curr_start(cfs_rq, se);
+	SCHED_WARN_ON(cfs_rq->curr);
 	cfs_rq->curr = se;
 
 	/*
@@ -5452,6 +5474,8 @@ set_next_entity(struct cfs_rq *cfs_rq, s
 	se->prev_sum_exec_runtime = se->sum_exec_runtime;
 }
```


```diff
+static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags);
+
 /*
  * Pick the next process, keeping these things in mind, in this order:
  * 1) keep things fair between processes/task groups
@@ -5460,16 +5484,29 @@ set_next_entity(struct cfs_rq *cfs_rq, s
  * 4) do not run the "skip" process, if something else is available
  */
 static struct sched_entity *
-pick_next_entity(struct cfs_rq *cfs_rq)
+pick_next_entity(struct rq *rq, struct cfs_rq *cfs_rq)
 {
 	/*
 	 * Enabling NEXT_BUDDY will affect latency but not fairness.
 	 */
 	if (sched_feat(NEXT_BUDDY) &&
-	    cfs_rq->next && entity_eligible(cfs_rq, cfs_rq->next))
+	    cfs_rq->next && entity_eligible(cfs_rq, cfs_rq->next)) {
+		/* ->next will never be delayed */
+		SCHED_WARN_ON(cfs_rq->next->sched_delayed);
 		return cfs_rq->next;
+	}
 
-	return pick_eevdf(cfs_rq);
+	struct sched_entity *se = pick_eevdf(cfs_rq);
+	if (se->sched_delayed) {
+		dequeue_entities(rq, se, DEQUEUE_SLEEP | DEQUEUE_DELAYED);
+		SCHED_WARN_ON(se->sched_delayed);
+		SCHED_WARN_ON(se->on_rq);
+		if (sched_feat(DELAY_ZERO) && se->vlag > 0)
+			se->vlag = 0;
+
+		return NULL;
+	}
+	return se;
 }
```

`set_next_entity` 與 `pick_next_entity` 做了稍許調整，包含幾個 `SCHED_WARN_ON()` 可以在早期去幫助發現 delay dequeue 實作上的缺漏。

另外就是在 `pick_eevdf()` 如果最終選到的是被 delay dequeue 的任務，則表示該任務已 eligible，因此可以正式被 dequeue。具體是在 `dequeue_entities` 加上額外的 `DEQUEUE_DELAYED`，則 `dequeue_entities` 會將此 flag 傳遞給 `dequeue_entity()`，即完成前面提到的 dequeue 的後半流程。

這邊還可以看到引入一個新的 feature `DELAY_ZERO`。前面有提到 delay dequeue 理想的完成點是 se 之 lag 為 0 時。真實場景上實作此有困難，但我們可以選擇此 feature 以直接將 lag 重設回 0

```diff
 static bool check_cfs_rq_runtime(struct cfs_rq *cfs_rq);
@@ -5493,6 +5530,7 @@ static void put_prev_entity(struct cfs_r
 		/* in !on_rq case, update occurred at dequeue */
 		update_load_avg(cfs_rq, prev, 0);
 	}
+	SCHED_WARN_ON(cfs_rq->curr != prev);
 	cfs_rq->curr = NULL;
 }
 
@@ -5793,6 +5831,10 @@ static bool throttle_cfs_rq(struct cfs_r
 		if (!se->on_rq)
 			goto done;
 
+		/*
+		 * XXX should be fine vs sched_delay; if won't run after this.
+		 * Either pick dequeues it, or unthrottle. Double check!!
+		 */
 		dequeue_entity(qcfs_rq, se, DEQUEUE_SLEEP);
 
 		if (cfs_rq_is_idle(group_cfs_rq(se)))
@@ -5882,8 +5924,11 @@ void unthrottle_cfs_rq(struct cfs_rq *cf
 	for_each_sched_entity(se) {
 		struct cfs_rq *qcfs_rq = cfs_rq_of(se);
 
-		if (se->on_rq)
+		if (se->on_rq) {
+			if (se->sched_delayed)
+				requeue_delayed_entity(se);
 			break;
+		}
 		enqueue_entity(qcfs_rq, se, ENQUEUE_WAKEUP);
 
 		if (cfs_rq_is_idle(group_cfs_rq(se)))
@@ -6729,6 +6774,40 @@ static int sched_idle_cpu(int cpu)
 }
 #endif
```

delay dequeue 可能影響的還有 `throttle_cfs_rq` 與 `unthrottle_cfs_rq`。在 throttle 方面可能是沒有影響的，因為對應任務即便被選中也就是完成後半步驟的 dequeue，不會有意外沒被 throttle 到的問題。

unthrottle 的情形下，如果 `se` 是 delayed dequeue 的狀態，我們需要重新 enqueue 之。但此時 `se` 雖仍在 queue 上，但邏輯上已經不在排隊。因此與一般的 se 處理方式不同，要藉由下面的 `requeue_delayed_entity` 來進行 enqueue。

```cpp
static void
requeue_delayed_entity(struct sched_entity *se)
{
	struct cfs_rq *cfs_rq = cfs_rq_of(se);

	/*
	 * se->sched_delayed should imply both: se->on_rq == 1 and
	 * cfs_rq->curr != se. Because a delayed entity is one that is still on
	 * the runqueue competing until elegibility.
	 *
	 * Except for groups, consider current going idle and newidle pulling a
	 * task in the same group -- in that case 'cfs_rq->curr == se'.
	 */
	SCHED_WARN_ON(!se->sched_delayed);
	SCHED_WARN_ON(!se->on_rq);
	SCHED_WARN_ON(entity_is_task(se) && cfs_rq->curr == se);

	if (sched_feat(DELAY_ZERO)) {
		update_entity_lag(cfs_rq, se);
		if (se->vlag > 0) {
			cfs_rq->nr_running--;
			if (se != cfs_rq->curr)
				__dequeue_entity(cfs_rq, se);
			se->vlag = 0;
			place_entity(cfs_rq, se, 0);
			if (se != cfs_rq->curr)
				__enqueue_entity(cfs_rq, se);
			cfs_rq->nr_running++;
		}
	}

	se->sched_delayed = 0;
}
```

`requeue_delayed_entity` 的作用是將邏輯上已被 dequeue，但因為 delay `se` 仍存在 queue 的任務進行 enqueue。

沒有使用 `DELAY_ZERO` 的情況下，`requeue_delayed_entity` 唯一需做的事是將 `sched_delayed` 重置成 0。由於 `se` 本來就還存在 queue 中，因此只是從 delayed dequeue 變成正常等待 CPU 資源的狀態。

使用 `DELAY_ZERO` 的情況比較複雜。因為我們想選擇讓 `se` 在邏輯的 dequeue 時 vlag 歸零而非繼承到下次 enqueue，需要真正將 `se` 從排程的紅黑樹上移除，vlag 重設為 0 後再重新插入到合適的位置上。


```diff
 /*
  * The enqueue_task method is called before nr_running is
  * increased. Here we update the fair scheduling stats and
@@ -6742,6 +6821,11 @@ enqueue_task_fair(struct rq *rq, struct
 	int idle_h_nr_running = task_has_idle_policy(p);
 	int task_new = !(flags & ENQUEUE_WAKEUP);
 
+	if (flags & ENQUEUE_DELAYED) {
+		requeue_delayed_entity(se);
+		return;
+	}
+
```

使用 `ENQUEUE_DELAYED` flag 進行 enqueue_task 代表已知要 enqueue 的是一個被 delayed dequeue 的任務。此時就通過前面介紹的 `requeue_delayed_entity` 完成即可。

```diff
 	/*
 	 * The code below (indirectly) updates schedutil which looks at
 	 * the cfs_rq utilization to select a frequency.
@@ -6759,8 +6843,11 @@ enqueue_task_fair(struct rq *rq, struct
 		cpufreq_update_util(rq, SCHED_CPUFREQ_IOWAIT);
 
 	for_each_sched_entity(se) {
-		if (se->on_rq)
+		if (se->on_rq) {
+			if (se->sched_delayed)
+				requeue_delayed_entity(se);
 			break;
+		}
 		cfs_rq = cfs_rq_of(se);
 		enqueue_entity(cfs_rq, se, flags);
```

一般的 enqueue 則可藉 `se->sched_delayed` 判斷 enqueue 該採用何種方式進行。

```diff
@@ -6836,6 +6923,7 @@ static int dequeue_entities(struct rq *r
 {
 	bool was_sched_idle = sched_idle_rq(rq);
 	bool task_sleep = flags & DEQUEUE_SLEEP;
+	bool task_delayed = flags & DEQUEUE_DELAYED;
 	struct task_struct *p = NULL;
 	struct cfs_rq *cfs_rq;
 	int idle_h_nr_running;
@@ -6849,7 +6937,13 @@ static int dequeue_entities(struct rq *r
 
 	for_each_sched_entity(se) {
 		cfs_rq = cfs_rq_of(se);
-		dequeue_entity(cfs_rq, se, flags);
+
+		if (!dequeue_entity(cfs_rq, se, flags)) {
+			if (p && &p->se == se)
+				return -1;
+
+			break;
+		}
```

在 `dequeue_entities` 中，如果 `dequeue_entity()` 回傳 false，代表本次 dequeue 需以 delay 方式處理。在 se 為對應到 task 之狀況下(`if (p && &p->se == se)`)，直接回傳 -1 表示 dequeue delayed。如果 se 對應到 task_group，則仍需要在下一個 `for_each_sched_entity()` 更新往上層級的 se 之 load 相關數據。

:::warning
不確定這裡的思考為何，在第二個 `for_each_sched_entity()` 我們可以看到留下 `// XXX avoid these load updates for delayed dequeues ?` 這段註解
:::

```diff
 		/* h_nr_running is the hierachical count of tasks */
 		if (p) {
@@ -6877,6 +6971,7 @@ static int dequeue_entities(struct rq *r
 			break;
 		}
 		flags |= DEQUEUE_SLEEP;
+		flags &= ~DEQUEUE_DELAYED;
 	}
```

`DEQUEUE_DELAYED` 應只處理於多層 cfs_rq 的第一階段，因此這裡將對應 flag 給清除。


```diff
 	for_each_sched_entity(se) {
@@ -6906,6 +7001,18 @@ static int dequeue_entities(struct rq *r
 		/* balance early to pull high priority tasks */
 		if (unlikely(!was_sched_idle && sched_idle_rq(rq)))
 			rq->next_balance = jiffies;
+
+		if (task_delayed) {
+			SCHED_WARN_ON(!task_sleep);
+			SCHED_WARN_ON(p->on_rq != 1);
+
+			/* Fix-up what dequeue_task_fair() skipped */
+			util_est_update(&rq->cfs, p, task_sleep);
+			hrtick_update(rq);
+
+			/* Fix-up what deactivate_task() skipped. */
+			WRITE_ONCE(p->on_rq, 0);
+		}
 	}
 
 	return 1;
```

```diff
@@ -6923,6 +7030,10 @@ static bool dequeue_task_fair(struct rq
 	if (dequeue_entities(rq, &p->se, flags) < 0)
 		return false;
 
+	/*
+	 * It doesn't make sense to update util_est for the delayed dequeue
+	 * case where ttwu will make it appear the sleep never happened.
+	 */
 	util_est_update(&rq->cfs, p, flags & DEQUEUE_SLEEP);
 	hrtick_update(rq);
 	return true;
```

:::danger
這部分不太確定QAQ
:::

```diff
@@ -8463,7 +8574,9 @@ static struct task_struct *pick_task_fai
 		if (unlikely(check_cfs_rq_runtime(cfs_rq)))
 			goto again;
 
-		se = pick_next_entity(cfs_rq);
+		se = pick_next_entity(rq, cfs_rq);
+		if (!se)
+			goto again;
 		cfs_rq = group_cfs_rq(se);
 	} while (cfs_rq);
```

由於 delayed dequeue 的存在，pick() 可能會出現挑到被 delay 的 se 的情況，此時就嘗試再挑下一個。

```diff
@@ -12803,10 +12916,17 @@ static void attach_task_cfs_rq(struct ta
 static void switched_from_fair(struct rq *rq, struct task_struct *p)
 {
 	detach_task_cfs_rq(p);
+	// XXX think harder on this
+	// this could actually be handled correctly I suppose; keep the whole
+	// se enqueued while boosted. horrible complexity though
+	p->se.sched_delayed = 0;
+	// XXX also vlag ?!?
 }
 
 static void switched_to_fair(struct rq *rq, struct task_struct *p)
 {
+	SCHED_WARN_ON(p->se.sched_delayed);
+
 	attach_task_cfs_rq(p);
 
 	set_task_max_allowed_capacity(p);
```

```diff
--- a/kernel/sched/features.h
+++ b/kernel/sched/features.h
@@ -29,6 +29,18 @@ SCHED_FEAT(NEXT_BUDDY, false)
 SCHED_FEAT(CACHE_HOT_BUDDY, true)

 /*
+ * Delay dequeueing tasks until they get selected or woken.
+ *
+ * By delaying the dequeue for non-eligible tasks, they remain in the
+ * competition and can burn off their negative lag. When they get selected
+ * they'll have positive lag by definition.
+ *
+ * DELAY_ZERO clips the lag on dequeue (or wakeup) to 0.
+ */
+SCHED_FEAT(DELAY_DEQUEUE, true)
+SCHED_FEAT(DELAY_ZERO, true)
+
+/*
```

前面引進的改動，目前是以 `DELAY_DEQUEUE` 和 `DELAY_ZERO` feature 留下選擇的空間，未直接成為固定行為。

```diff
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -2245,6 +2245,7 @@ extern const u32		sched_prio_to_wmult[40
 #define DEQUEUE_MOVE		0x04 /* Matches ENQUEUE_MOVE */
 #define DEQUEUE_NOCLOCK		0x08 /* Matches ENQUEUE_NOCLOCK */
 #define DEQUEUE_MIGRATING	0x100 /* Matches ENQUEUE_MIGRATING */
+#define DEQUEUE_DELAYED		0x200 /* Matches ENQUEUE_DELAYED */
 
 #define ENQUEUE_WAKEUP		0x01
 #define ENQUEUE_RESTORE		0x02
@@ -2260,6 +2261,7 @@ extern const u32		sched_prio_to_wmult[40
 #endif
 #define ENQUEUE_INITIAL		0x80
 #define ENQUEUE_MIGRATING	0x100
+#define ENQUEUE_DELAYED		0x200
 
 #define RETRY_TASK		((void *)-1UL)
```

根據前述改動定義的兩個新的 enqueue/dequeue flag。

### [RFC][PATCH 09/10] sched/eevdf: Allow shorter slices to wakeup-preempt

```diff
--- a/kernel/sched/features.h
+++ b/kernel/sched/features.h
@@ -14,6 +14,10 @@ SCHED_FEAT(PLACE_DEADLINE_INITIAL, true)
  * 0-lag point or until is has exhausted it's slice.
  */
 SCHED_FEAT(RUN_TO_PARITY, true)
+/*
+ * Allow tasks with a shorter slice to disregard RUN_TO_PARITY
+ */
+SCHED_FEAT(PREEMPT_SHORT, true)
 
```

此改動新增一個新的 scheduler feature `PREEMPT_SHORT`。

```diff
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -8538,9 +8538,14 @@ static void check_preempt_wakeup_fair(st
 	cfs_rq = cfs_rq_of(se);
 	update_curr(cfs_rq);
 
-	/*
-	 * XXX pick_eevdf(cfs_rq) != se ?
-	 */
+	if (sched_feat(PREEMPT_SHORT) && pse->slice < se->slice &&
+	    entity_eligible(cfs_rq, pse) &&
+	    (s64)(pse->deadline - se->deadline) < 0 &&
+	    se->vlag == se->deadline) {
+		/* negate RUN_TO_PARITY */
+		se->vlag = se->deadline - 1;
+	}
+
 	if (pick_eevdf(cfs_rq) == pse)
 		goto preempt;
```

回顧之前在 [PATCH 2/4 sched/eevdf: Sort the rbtree by virtual deadline](https://hackmd.io/1wvEMFFVQ5ui4iPd5fPfIg?view#PATCH-24-schedeevdf-Sort-the-rbtree-by-virtual-deadline) 談到 `RUN_TO_PARITY` 的功能。當 `se->vlag == se->deadline` 成立於一個 `se->on_rq == true` 的 se 時，就表示該 se 在獲得排程之後，尚未執行足一個 time slice。在開啟 `RUN_TO_PARITY` 的狀況下，`pick_eevdf()` 會繼續選擇該任務，即便被喚醒(woken up) 的任務之 deadline 更短，以確保其不被搶佔。

:::warning
原文稱之為 "Run the entity to parity"，但不確定中文上該怎麼翻譯 parity 一詞較為恰當。
:::

為了在這系列 patch 引入每個任務可以有不同 time slice/request size 的機制，`PREEMPT_SHORT` 在 `RUN_TO_PARITY` 的基礎之上，對上述可以 "run to parity" 的 se 附加額外條件: 只有在該 se 的 time slice 不比原本預期會搶佔的任務長時，才可以 "run to parity"。

畢竟，在 EEVDF 中，被分配較短 time slice/request size 的任務被預期的特性是更高的反應力(responsiveness)，如果因為 `RUN_TO_PARITY` 而失去此特性，可能並非樂見的排程方式。

在 https://lore.kernel.org/lkml/20240405110010.788110341@infradead.org/ 可以看到支持此改動必要的實驗數據。

### [RFC][PATCH 10/10] sched/eevdf: Use sched_attr::sched_runtime to set request/slice suggestion

在引入 slice/request size 的機制後，這個 patch 允許使用者可以通過 [sched_setattr()](https://man7.org/linux/man-pages/man2/sched_setattr.2.html) 來設置之。這裡需要注意的是: `sched_runtime` 並不是 EEVDF 新增的 attribute。只不過這個參數原本主要是設計給 `SCHED_DEADLINE`，而現在則為 `SCHED_FAIR` 的 EEVDF 所借用。

:::info
[`sched_runtime`](https://elixir.bootlin.com/linux/v6.10.5/source/include/uapi/linux/sched/types.h#L111) 在註解上或許可以稍做調整，避免誤解。
:::

```diff
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -542,7 +542,10 @@ struct sched_entity {
 
 	struct list_head		group_node;
 	unsigned int			on_rq;
-	unsigned int			sched_delayed;
+
+	unsigned char			sched_delayed;
+	unsigned char			custom_slice;
+					/* 2 byte hole */
 
 	u64				exec_start;
 	u64				sum_exec_runtime;
```

引入 sched_entity 的新成員 `custom_slice`，這是一個 0 或 1 的變數，代表使用者是否設置了客製化的 slice。

此外，`sched_delayed` 我們之前已經提到過，由於這也是一個 0 或 1 的變數，這裡型別可以從 `int` 改為 `char`。則加入 `custom_slice` 後對 struct 整體的大小可以保持不變。


```diff
@@ -4550,7 +4550,6 @@ static void __sched_fork(unsigned long c
 	p->se.nr_migrations		= 0;
 	p->se.vruntime			= 0;
 	p->se.vlag			= 0;
-	p->se.slice			= sysctl_sched_base_slice;
 	INIT_LIST_HEAD(&p->se.group_node);
```

`__sched_fork` 中排除了對 slice 的直接初始化。取而代之的，被 fork 的新 se 可能繼承客製定義的 slice/`sched_runtime`，或者因為 `sched_reset_on_fork` 才重新初始化(見後文)。

```diff
int sched_fork(unsigned long clone_flags, struct task_struct *p)
{
	__sched_fork(clone_flags, p);
	
	...
	
	/*
	 * Revert to default priority/policy on fork if requested.
	 */
	if (unlikely(p->sched_reset_on_fork)) {
		...
 		p->prio = p->normal_prio = p->static_prio;
 		set_load_weight(p, false);
+		p->se.custom_slice = 0;
+		p->se.slice = sysctl_sched_base_slice;
```

這裡調整 fork 時對 slice 的初始化方式，如果 `sched_reset_on_fork` 為 true，這代表 fork 之後的新任務會和 parent 的一些特徵脫鉤，因此包含新增的 custom_slice 和 slice 的都需要重新初始化為預設值。

```diff
@@ -7627,10 +7628,20 @@ static void __setscheduler_params(struct
 
 	p->policy = policy;
 
-	if (dl_policy(policy))
+	if (dl_policy(policy)) {
 		__setparam_dl(p, attr);
-	else if (fair_policy(policy))
+	} else if (fair_policy(policy)) {
 		p->static_prio = NICE_TO_PRIO(attr->sched_nice);
+		if (attr->sched_runtime) {
+			p->se.custom_slice = 1;
+			p->se.slice = clamp_t(u64, attr->sched_runtime,
+					      NSEC_PER_MSEC/10,   /* HZ=1000 * 10 */
+					      NSEC_PER_MSEC*100); /* HZ=100  / 10 */
+		} else {
+			p->se.custom_slice = 0;
+			p->se.slice = sysctl_sched_base_slice;
+		}
+	}
 
```

在 `__setscheduler_params()` 時考慮 `sched_runtime`:
* 如果未設置該 attribute，就使用預設的  `sysctl_sched_base_slice`(0.75 ms, 但可藉 debugfs 調整)
* 否則使用 `sched_runtime` 給定的。留意到實際值會根據給定值 clamp 到 0.1ms 至 100ms 之間

:::danger
選定這個範圍的理由是甚麼?
:::

```diff
@@ -7812,7 +7823,9 @@ static int __sched_setscheduler(struct t
 	 * but store a possible modification of reset_on_fork.
 	 */
 	if (unlikely(policy == p->policy)) {
-		if (fair_policy(policy) && attr->sched_nice != task_nice(p))
+		if (fair_policy(policy) &&
+		    (attr->sched_nice != task_nice(p) ||
+		     (attr->sched_runtime && attr->sched_runtime != p->se.slice)))
 			goto change;
 		if (rt_policy(policy) && attr->sched_priority != p->rt_priority)
 			goto change;
@@ -7958,6 +7971,9 @@ static int _sched_setscheduler(struct ta
 		.sched_nice	= PRIO_TO_NICE(p->static_prio),
 	};
 
+	if (p->se.custom_slice)
+		attr.sched_runtime = p->se.slice;
+
 	/* Fixup the legacy SCHED_RESET_ON_FORK hack. */
 	if ((policy != SETPARAM_POLICY) && (policy & SCHED_RESET_ON_FORK)) {
 		attr.sched_flags |= SCHED_FLAG_RESET_ON_FORK;
```

```diff
@@ -8124,12 +8140,14 @@ static int sched_copy_attr(struct sched_
 
 static void get_params(struct task_struct *p, struct sched_attr *attr)
 {
-	if (task_has_dl_policy(p))
+	if (task_has_dl_policy(p)) {
 		__getparam_dl(p, attr);
-	else if (task_has_rt_policy(p))
+	} else if (task_has_rt_policy(p)) {
 		attr->sched_priority = p->rt_priority;
-	else
+	} else {
 		attr->sched_nice = task_nice(p);
+		attr->sched_runtime = p->se.slice;
+	}
 }
```

`__sched_setscheduler()`/`get_params()` 對應 `sched_runtime` 的改動。

```diff
 /**
@@ -10084,6 +10102,7 @@ void __init sched_init(void)
 	}
 
 	set_load_weight(&init_task, false);
+	init_task.se.slice = sysctl_sched_base_slice,
```

這是對應前面 `__sched_fork()` 捨棄了對 slice 的初始化。因此對於沒有 parent 的 `init_task` 而言，需要另行設置。

```diff
--- a/kernel/sched/debug.c
+++ b/kernel/sched/debug.c
@@ -579,11 +579,12 @@ print_task(struct seq_file *m, struct rq
 	else
 		SEQ_printf(m, " %c", task_state_to_char(p));
 
-	SEQ_printf(m, "%15s %5d %9Ld.%06ld %c %9Ld.%06ld %9Ld.%06ld %9Ld.%06ld %9Ld %5d ",
+	SEQ_printf(m, "%15s %5d %9Ld.%06ld %c %9Ld.%06ld %c %9Ld.%06ld %9Ld.%06ld %9Ld %5d ",
 		p->comm, task_pid_nr(p),
 		SPLIT_NS(p->se.vruntime),
 		entity_eligible(cfs_rq_of(&p->se), &p->se) ? 'E' : 'N',
 		SPLIT_NS(p->se.deadline),
+		p->se.custom_slice ? 'S' : ' ',
 		SPLIT_NS(p->se.slice),
 		SPLIT_NS(p->se.sum_exec_runtime),
 		(long long)(p->nvcsw + p->nivcsw),
```

在 sched 的 debugfs 資訊中，顯示是否啟用 `custom_slice`(`'S'`)或否(`' '`)。


```diff
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -984,7 +984,8 @@ static void update_deadline(struct cfs_r
 	 * nice) while the request time r_i is determined by
 	 * sysctl_sched_base_slice.
 	 */
-	se->slice = sysctl_sched_base_slice;
+	if (!se->custom_slice)
+		se->slice = sysctl_sched_base_slice;
 
 	/*
 	 * EEVDF: vd_i = ve_i + r_i / w_i
@@ -5169,7 +5170,8 @@ place_entity(struct cfs_rq *cfs_rq, stru
 	u64 vslice, vruntime = avg_vruntime(cfs_rq);
 	s64 lag = 0;
 
-	se->slice = sysctl_sched_base_slice;
+	if (!se->custom_slice)
+		se->slice = sysctl_sched_base_slice;
 	vslice = calc_delta_fair(se->slice, se);
 
 	/*
```

則在不使用 `custom_slice` 的情況下，任務的 request size/time slice 就以預設的  `sysctl_sched_base_slice` 來設定。

## Reference

* [[RFC][PATCH 00/10] sched/fair: Complete EEVDF](https://lore.kernel.org/lkml/20240405102754.435410987@infradead.org/?fbclid=IwAR22R73s3oT78BcbvQjjgKLJJPUqjS2YRsESUY3f67CmTsZPj6EXCV-Omzs)