当前位置: 首页 > news >正文

kernel 6.6中新增的EEVDF特性

一.pick_next_entity 选择下一个任务

/*
5393   * Pick the next process, keeping these things in mind, in this order:
5394   * 1) keep things fair between processes/task groups
5395   * 2) pick the "next" process, since someone really wants that to run
5396   * 3) pick the "last" process, for cache locality
5397   * 4) do not run the "skip" process, if something else is available
5398   */
5399  static struct sched_entity *
5400  pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
5401  {
5402  	/*
5403  	 * Enabling NEXT_BUDDY will affect latency but not fairness.
5404  	 */
5405  	if (sched_feat(NEXT_BUDDY) &&
5406  	    cfs_rq->next && entity_eligible(cfs_rq, cfs_rq->next))
5407  		return cfs_rq->next;
5408  
5409  	return pick_eevdf(cfs_rq);
5410  }

Linux 内核调度器中用于选择下一个任务的核心逻辑之一,它在 pick_next_entity 函数中实现。虽然 CFS 调度器传统上依赖于 虚拟运行时间(vruntime 来实现公平调度,但从 Linux 6.6 开始,内核引入了新的调度算法 EEVDF(Earliest Eligible Virtual Deadline First),这使得任务选择的逻辑更加复杂且高效。

1. 函数背景

pick_next_entity 的作用是从当前调度队列(cfs_rq)中选择下一个调度实体(sched_entity)。这个函数的核心目标是:

  • 公平性:确保任务之间的时间分配公平。
  • 效率:尽可能减少延迟和上下文切换开销。
  • 缓存局部性:优先选择最近运行过的任务以提高性能。

在 Linux 6.6 中,调度器不再仅仅依赖 vruntime,而是结合了 EEVDF 和其他优化策略(如 NEXT_BUDDY)来选择任务。

2. 主要逻辑流程

(1) NEXT_BUDDY 优化
if (sched_feat(NEXT_BUDDY) &&cfs_rq->next && entity_eligible(cfs_rq, cfs_rq->next))return cfs_rq->next;
  • NEXT_BUDDY 功能
    • NEXT_BUDDY 是一个调度特性(feature),旨在通过“伙伴任务”机制减少延迟。
    • 如果启用了 NEXT_BUDDY,并且 cfs_rq->next 存在一个有效的“伙伴任务”,则直接返回该任务。
  • entity_eligible 检查
    • 确保“伙伴任务”符合调度条件(例如,它必须是可运行的)。
  • 优点
    • 这种优化可以显著减少延迟,尤其是在某些实时性要求较高的场景中。
    • 它不会影响公平性,因为 EEVDF 仍然会在后续步骤中处理公平性问题。
(2) EEVDF 调度算法
return pick_eevdf(cfs_rq);
  • 如果没有启用 NEXT_BUDDY 或者没有合适的“伙伴任务”,则调用 pick_eevdf 函数选择下一个任务。
  • EEVDF 的核心思想
    • EEVDF 是一种基于虚拟截止时间(virtual deadline)的调度算法。
    • 每个任务都有一个虚拟截止时间,调度器会优先选择最早到达虚拟截止时间的任务。
    • 这种算法可以更好地平衡公平性和延迟,尤其是在高负载或多任务场景中。

3. EEVDF 的工作原理

EEVDF 是 Linux 6.6 中引入的新调度算法,相比传统的 vruntime 排序,EEVDF 提供了更高效的调度决策。以下是其核心概念:

(1) 虚拟截止时间

每个任务的虚拟截止时间(deadline)由以下公式计算:

deadline = vruntime + period
  • vruntime:任务的虚拟运行时间。
  • period:一个固定的时间间隔,表示任务的目标运行周期。

调度器会选择虚拟截止时间最小的任务作为下一个运行任务。

(2) 与 vruntime 的区别
  • 在传统的 CFS 中,任务按照 vruntime 排序,可能导致高优先级任务被低优先级任务抢占。
  • 在 EEVDF 中,任务不仅考虑 vruntime,还考虑虚拟截止时间,从而更好地平衡公平性和延迟。

二.EEVDF(Earliest Eligible Virtual Deadline First) 调度算法的核心逻辑

/*
904   * Earliest Eligible Virtual Deadline First
905   *
906   * In order to provide latency guarantees for different request sizes
907   * EEVDF selects the best runnable task from two criteria:
908   *
909   *  1) the task must be eligible (must be owed service)
910   *
911   *  2) from those tasks that meet 1), we select the one
912   *     with the earliest virtual deadline.
913   *
914   * We can do this in O(log n) time due to an augmented RB-tree. The
915   * tree keeps the entries sorted on deadline, but also functions as a
916   * heap based on the vruntime by keeping:
917   *
918   *  se->min_vruntime = min(se->vruntime, se->{left,right}->min_vruntime)
919   *
920   * Which allows tree pruning through eligibility.
921   */
922  static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq)
923  {
924  	struct rb_node *node = cfs_rq->tasks_timeline.rb_root.rb_node;
925  	struct sched_entity *se = __pick_first_entity(cfs_rq);
926  	struct sched_entity *curr = cfs_rq->curr;
927  	struct sched_entity *best = NULL;
928  
929  	/*
930  	 * We can safely skip eligibility check if there is only one entity
931  	 * in this cfs_rq, saving some cycles.
932  	 */
933  	if (cfs_rq->nr_running == 1)
934  		return curr && curr->on_rq ? curr : se;
935  
936  	if (curr && (!curr->on_rq || !entity_eligible(cfs_rq, curr)))
937  		curr = NULL;
938  
939  	/*
940  	 * Once selected, run a task until it either becomes non-eligible or
941  	 * until it gets a new slice. See the HACK in set_next_entity().
942  	 */
943  	if (sched_feat(RUN_TO_PARITY) && curr && curr->vlag == curr->deadline)
944  		return curr;
945  
946  	/* Pick the leftmost entity if it's eligible */
947  	if (se && entity_eligible(cfs_rq, se)) {
948  		best = se;
949  		goto found;
950  	}
951  
952  	/* Heap search for the EEVD entity */
953  	while (node) {
954  		struct rb_node *left = node->rb_left;
955  
956  		/*
957  		 * Eligible entities in left subtree are always better
958  		 * choices, since they have earlier deadlines.
959  		 */
960  		if (left && vruntime_eligible(cfs_rq,
961  					__node_2_se(left)->min_vruntime)) {
962  			node = left;
963  			continue;
964  		}
965  
966  		se = __node_2_se(node);
967  
968  		/*
969  		 * The left subtree either is empty or has no eligible
970  		 * entity, so check the current node since it is the one
971  		 * with earliest deadline that might be eligible.
972  		 */
973  		if (entity_eligible(cfs_rq, se)) {
974  			best = se;
975  			break;
976  		}
977  
978  		node = node->rb_right;
979  	}
980  found:
981  	if (!best || (curr && entity_before(curr, best)))
982  		best = curr;
983  
984  	return best;
985  }

1. 函数背景

pick_eevdf 的目标是从当前调度队列(cfs_rq)中选择一个符合条件的任务。它的选择逻辑基于以下两个条件:

  1. 任务必须是“可选的”(eligible)
    • 任务需要满足一定的服务欠账条件(即它需要被分配 CPU 时间)。
  2. 从符合条件的任务中,选择虚拟截止时间最早的任务
    • 虚拟截止时间(deadline)越早,任务优先级越高。

2. 主要逻辑流程

(1) 单任务优化
if (cfs_rq->nr_running == 1)return curr && curr->on_rq ? curr : se;
  • 如果当前调度队列中只有一个任务,则直接返回该任务。
  • 如果当前任务(curr)在运行队列上(on_rq),则返回当前任务;否则返回红黑树中最左边的任务(se)。
  • 这种优化避免了不必要的复杂逻辑,提高了效率。
(2) 当前任务的检查
if (curr && (!curr->on_rq || !entity_eligible(cfs_rq, curr)))curr = NULL;
  • 检查当前任务是否仍然在运行队列上(on_rq)并且是否符合“可选性”条件(entity_eligible)。
  • 如果当前任务不符合条件,则将其置为 NULL
(3) RUN_TO_PARITY 特性
if (sched_feat(RUN_TO_PARITY) && curr && curr->vlag == curr->deadline)return curr;
  • 如果启用了 RUN_TO_PARITY 特性,并且当前任务的虚拟标记(vlag)等于其虚拟截止时间(deadline),则直接返回当前任务。
  • RUN_TO_PARITY 的作用
    • 确保任务在其虚拟截止时间到达之前尽可能多地运行,从而减少上下文切换开销。
(4) 左子树优先选择
if (se && entity_eligible(cfs_rq, se)) {best = se;goto found;
}
  • 检查红黑树中最左边的任务(se)是否符合条件。
  • 如果符合条件,则直接选择该任务,并跳转到 found 标签。
(5) 堆搜索(Heap Search)
while (node) {struct rb_node *left = node->rb_left;if (left && vruntime_eligible(cfs_rq,__node_2_se(left)->min_vruntime)) {node = left;continue;}se = __node_2_se(node);if (entity_eligible(cfs_rq, se)) {best = se;break;}node = node->rb_right;
}
  • 左子树优先
    • 如果左子树存在并且其中的任务符合条件,则继续向左遍历。
    • 左子树中的任务总是具有更早的虚拟截止时间,因此优先考虑。
  • 当前节点检查
    • 如果左子树不包含符合条件的任务,则检查当前节点的任务。
    • 如果当前节点的任务符合条件,则选择该任务并退出循环。
  • 右子树遍历
    • 如果当前节点的任务不符合条件,则继续向右遍历。
(6) 最终选择
if (!best || (curr && entity_before(curr, best)))best = curr;
  • 如果没有找到符合条件的任务,或者当前任务比找到的任务更优(entity_before),则选择当前任务。
  • entity_before 的作用
    • 比较两个任务的虚拟运行时间(vruntime),选择较小的那个。

3. 关键点解析

(1) 可选性(Eligibility)
  • 定义
    • 一个任务被认为是“可选的”,当它的虚拟运行时间(vruntime)小于或等于调度队列的最小虚拟运行时间(min_vruntime)。
  • 作用
    • 确保任务不会因为不公平的调度而被饿死。
(2) 虚拟截止时间(Deadline)
  • 计算公式
deadline = vruntime + period
  • period 是一个固定的时间间隔,表示任务的目标运行周期。
(3) 红黑树的双重角色
  • 按虚拟截止时间排序
    • 红黑树按照虚拟截止时间(deadline)对任务进行排序,确保可以快速找到最早的任务。
  • 按虚拟运行时间堆化
    • 每个节点维护一个 min_vruntime,表示该子树中所有任务的最小虚拟运行时间。
    • 这种设计允许通过剪枝(pruning)快速排除不符合条件的任务。

4. 示例场景

假设一个系统中有以下任务:

  • 任务 A:vruntime = 100deadline = 150
  • 任务 B:vruntime = 90deadline = 140
  • 任务 C:vruntime = 80deadline = 160
(1) 单任务优化
  • 如果调度队列中只有任务 A,则直接返回任务 A。
(2) 当前任务检查
  • 如果当前任务是任务 B,但任务 B 不符合条件(例如,不在运行队列上),则将其置为 NULL
(3) 左子树优先选择
  • 如果红黑树中最左边的任务是任务 B,并且任务 B 符合条件,则直接选择任务 B。
(4) 堆搜索
  • 遍历红黑树,首先检查左子树中的任务。
  • 如果左子树中没有符合条件的任务,则检查当前节点的任务。
  • 如果当前节点的任务符合条件,则选择该任务。

5. 总结

pick_eevdf 的核心逻辑可以分为以下几个步骤:

  1. 单任务优化:如果只有一个任务,则直接返回。
  2. 当前任务检查:确保当前任务符合条件。
  3. 左子树优先选择:优先选择红黑树中最左边的任务。
  4. 堆搜索:通过红黑树的剪枝机制快速找到符合条件的任务。
  5. 最终选择:比较当前任务和找到的任务,选择最优的任务。

三.EEVDF 的实现细节

int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se)
786  {
787  	return vruntime_eligible(cfs_rq, se->vruntime);
788  }/*
753   * Entity is eligible once it received less service than it ought to have,
754   * eg. lag >= 0.
755   *
756   * lag_i = S - s_i = w_i*(V - v_i)
757   *
758   * lag_i >= 0 -> V >= v_i
759   *
760   *     \Sum (v_i - v)*w_i
761   * V = ------------------ + v
762   *          \Sum w_i
763   *
764   * lag_i >= 0 -> \Sum (v_i - v)*w_i >= (v_i - v)*(\Sum w_i)
765   *
766   * Note: using 'avg_vruntime() > se->vruntime' is inacurate due
767   *       to the loss in precision caused by the division.
768   */
769  static int vruntime_eligible(struct cfs_rq *cfs_rq, u64 vruntime)
770  {
771  	struct sched_entity *curr = cfs_rq->curr;
772  	s64 avg = cfs_rq->avg_vruntime;
773  	long load = cfs_rq->avg_load;
774  
775  	if (curr && curr->on_rq) {
776  		unsigned long weight = scale_load_down(curr->load.weight);
777  
778  		avg += entity_key(cfs_rq, curr) * weight;
779  		load += weight;
780  	}
781  
782  	return avg >= (s64)(vruntime - cfs_rq->min_vruntime) * load;
783  }
784  

1. 传统 CFS 的问题

在传统的 CFS 调度器中,任务的选择主要依赖于 vruntime

  • 公平性:通过 vruntime 确保所有任务的 CPU 时间分配是公平的。
  • 延迟问题
    • 高负载场景下,长任务可能会被频繁抢占,导致延迟增加。
    • 短任务可能会因为长任务的高 vruntime 而等待过久。

这些问题的核心在于 vruntime 只考虑了运行时间,而没有考虑任务的“需求”(如任务的优先级或服务欠账)


2. EEVDF 的改进

EEVDF 的设计目标是通过引入 虚拟截止时间(deadline服务延迟(lag) 的概念来解决上述问题。以下是 EEVDF 的关键特性及其相对于传统 vruntime 的优势:

(1) 引入虚拟截止时间
  • 每个任务都有一个虚拟截止时间(deadline),计算公式为:
deadline = vruntime + period
  • period 是一个固定的时间间隔,表示任务的目标运行周期。
  • 作用
    • 虚拟截止时间决定了任务的优先级,调度器会选择最早到达虚拟截止时间的任务。
    • 这种机制确保短任务能够快速得到调度,从而减少延迟。
(2) 引入服务延迟(Eligibility)
  • 服务欠账的定义
    • 任务的服务欠账(lag)表示它实际获得的服务量与其应得服务量之间的差距。
    • 公式为:

lag_i = S - s_i = w_i * (V - v_i)

  • S:系统总服务量。
  • s_i:任务 i 实际获得的服务量。
  • w_i:任务 i 的权重。
  • V:系统的平均虚拟运行时间。
  • v_i:任务 i 的虚拟运行时间。
  • 条件
    • 如果 lag_i >= 0,则任务被认为是“可选的”(eligible)。
    • 这意味着任务需要更多的服务,调度器会优先选择它。
  • 作用
    • 通过服务欠账的计算,确保任务不会因为不公平的调度而被饿死。
    • 这种机制使得 EEVDF 在高负载场景下表现更好。
(3) 平均虚拟运行时间(avg_vruntime
  • vruntime_eligible 函数中,计算了系统的平均虚拟运行时间(avg_vruntime):
s64 avg = cfs_rq->avg_vruntime;
773  	long load = cfs_rq->avg_load;
774  
775  	if (curr && curr->on_rq) {
776  		unsigned long weight = scale_load_down(curr->load.weight);
777  
778  		avg += entity_key(cfs_rq, curr) * weight;
779  		load += weight;
780  	}
  • avg_vruntime 表示所有任务的加权平均虚拟运行时间。
  • 它用于判断某个任务是否符合“可选性”条件。
  • 作用
    • avg_vruntime 提供了一个全局视角,避免了传统 CFS 中因单个任务 vruntime 偏移而导致的不公平现象。

3. vruntime_eligible 的逻辑解析

static int vruntime_eligible(struct cfs_rq *cfs_rq, u64 vruntime)
{struct sched_entity *curr = cfs_rq->curr;s64 avg = cfs_rq->avg_vruntime;long load = cfs_rq->avg_load;if (curr && curr->on_rq) {unsigned long weight = scale_load_down(curr->load.weight);avg += entity_key(cfs_rq, curr) * weight;load += weight;}return avg >= (s64)(vruntime - cfs_rq->min_vruntime) * load;
}
(1) 计算平均虚拟运行时间
  • avg_vruntime 是所有任务的加权平均虚拟运行时间。
  • 如果当前任务(curr)在运行队列上,则将其贡献加入到 avg_vruntimeload 中。
(2) 判断可选性
  • 最终返回值是一个布尔值,判断以下条件是否成立:
avg >= (vruntime - cfs_rq->min_vruntime) * load
  • 如果成立,则任务被认为是“可选的”。
  • 这个条件结合了 avg_vruntimevruntimemin_vruntime,确保任务的选择既公平又高效。

4. EEVDF 的优势总结

相比传统 vruntime,EEVDF 的改进体现在以下几个方面:

  1. 更好的延迟控制
    • 通过虚拟截止时间(deadline),确保短任务能够快速得到调度。
  2. 更高的公平性
    • 通过服务欠账(lag)和平均虚拟运行时间(avg_vruntime),确保任务不会因为不公平的调度而被饿死。
  3. 更高效的搜索
    • 红黑树既按虚拟截止时间排序,又按虚拟运行时间堆化,支持 O(log n) 时间内找到符合条件的任务。
  4. 动态适应性
    • 通过加权平均和动态调整,EEVDF 能够更好地适应高负载和多任务场景。

5. 示例对比

假设一个系统中有以下任务:

  • 任务 A:vruntime = 100deadline = 150
  • 任务 B:vruntime = 90deadline = 140
  • 任务 C:vruntime = 80deadline = 160
(1) 传统 CFS
  • 传统 CFS 仅根据 vruntime 排序,选择任务 C(vruntime = 80)。
  • 但如果任务 C 是一个长任务,可能会导致短任务(如任务 B)等待过久。
(2) EEVDF
  • EEVDF 根据虚拟截止时间选择任务 B(deadline = 140)。
  • 同时,通过服务欠账的计算,确保任务 A 和任务 C 不会被饿死。

6. 总结

虽然从代码表面看,EEVDF 的实现与传统 CFS 类似,但其核心思想和机制有着本质的不同。EEVDF 通过引入虚拟截止时间和服务欠账的概念,解决了传统 CFS 在高负载和多任务场景下的延迟和公平性问题。

**附加知识

lag_i = S - s_i = w_i * (V - v_i)

1. 公式的理论背景

公式 lag_i = S - s_i = w_i * (V - v_i) 是 EEVDF 的核心理论之一,用于衡量任务的服务欠账(lag)。以下是公式的分解:

(1) 符号解释
  • S:系统总服务量。
  • s_i:任务 i 实际获得的服务量。
  • w_i:任务 i 的权重。
  • V:系统的平均虚拟运行时间。
  • v_i:任务 i 的虚拟运行时间。
(2) 公式的含义
  • 服务欠账(lag
    • 如果 lag_i >= 0,表示任务 i 获得的服务量少于它应得的服务量,因此它是“可选的”(eligible)。
    • 如果 lag_i < 0,表示任务 i 已经获得了足够的服务,暂时不需要被调度。
  • 加权差值
    • 公式的核心是通过加权差值(w_i * (V - v_i))来计算服务欠账。
    • 加权差值考虑了任务的权重(优先级),确保高优先级任务能够更快地获得服务。

2. 公式在代码中的隐含实现

虽然代码中没有直接使用 lag_i = w_i * (V - v_i),但它的逻辑已经通过其他方式体现出来。以下是具体分析:

(1) 平均虚拟运行时间(avg_vruntime
s64 avg = cfs_rq->avg_vruntime;
long load = cfs_rq->avg_load;if (curr && curr->on_rq) {unsigned long weight = scale_load_down(curr->load.weight);avg += entity_key(cfs_rq, curr) * weight;load += weight;
}
  • avg_vruntime:表示所有任务的加权平均虚拟运行时间(V)。
  • load:表示所有任务的总权重。
  • 通过这两个变量,代码隐含地计算了系统的平均虚拟运行时间(V)。
(2) 判断服务欠账
return avg >= (s64)(vruntime - cfs_rq->min_vruntime) * load;
  • vruntime - cfs_rq->min_vruntime
    • 表示当前任务的虚拟运行时间与调度队列的最小虚拟运行时间之间的差距。
    • 这个值可以看作是任务的相对延迟。
  • avg >= ... * load
    • 这里的 avgload 结合起来,实际上是在判断任务的服务欠账是否为正。
    • 如果条件成立,则任务被认为是“可选的”。
(3) 隐含的公式变形

通过对代码的分析,我们可以将公式 lag_i = w_i * (V - v_i) 变形为以下形式:

lag_i ≈ load * (avg_vruntime - vruntime)
  • avg_vruntime 对应公式的 V
  • vruntime 对应公式的 v_i
  • load 对应公式的权重部分。

代码中的 avg >= (vruntime - cfs_rq->min_vruntime) * load 实际上是对上述变形公式的实现。

3. 为什么没有直接实现公式?

Linux 内核在实现 EEVDF 时,对公式进行了以下优化和简化:

  1. 避免浮点运算
    • 内核中尽量避免使用浮点运算,以提高效率。
    • 因此,公式中的权重(w_i)和虚拟运行时间(v_i)都通过整数运算实现。
  2. 减少计算复杂度
    • 直接计算 lag_i 需要遍历所有任务,计算其权重和服务量。这会显著增加计算开销。
    • 通过维护 avg_vruntimeload,内核能够在 O(1) 时间内判断任务的可选性。
  3. 动态调整
    • avg_vruntimeload 是动态更新的,能够快速适应系统状态的变化。

4. 示例对比

假设一个系统中有以下任务:

  • 任务 A:vruntime = 100,权重 w = 1
  • 任务 B:vruntime = 90,权重 w = 2
  • 系统的 avg_vruntime = 95load = 3
(1) 计算服务欠账

根据公式 lag_i = w_i * (V - v_i)

  • 任务 A

lag_A = 1 * (95 - 100) = -5

  • 任务 A 的服务欠账为负,表示它已经获得了足够的服务。

任务 B

lag_B = 2 * (95 - 90) = 10

  • 任务 B 的服务欠账为正,表示它需要更多的服务。
(2) 代码中的实现

在代码中,判断条件为:

avg >= (s64)(vruntime - cfs_rq->min_vruntime) * load

任务 A

95 >= (100 - 95) * 3 => 95 >= 15

  • 条件成立,任务 A 符合条件。

任务 B

95 >= (90 - 95) * 3 => 95 >= -15

  • 条件成立,任务 B 符合条件。

综合下来任务B可能更加符合

http://www.dtcms.com/a/394450.html

相关文章:

  • MATLAB M代码解释器设计与C++实现
  • nivida jetson orinnx torch环境搭建
  • Java进阶教程,全面剖析Java多线程编程,线程的生命周期,笔记11
  • Javase 基础加强 —— 12 网络编程
  • 【04】EPGF 架构搭建教程之 工具环境变量的配置
  • Oracle -运维学习路线 --学习篇1
  • 三个余弦:平方和凑成1时会发生什么
  • 碧蓝航线装备参数探究——关于金色之耻
  • Golang圖書館
  • linux命令--迭代积累
  • Unity2D-物理系统
  • 崩铁 预言算牌 解谜
  • 解锁AI巨型模型训练:DeepSpeed ZeRO Offload 全面指南
  • python语言中的常用容器(集合)
  • Python 程序控制流程综合编程
  • Java进阶教程,全面剖析Java多线程编程,同步方法,笔记13
  • 1.6 虚拟机 (答案见原书 P33)
  • 【C++练习】26.在 C++ 中,不使用 “strcpy“ 函数将一个字符串复制到另一个字符串
  • 【分布式技术】Baerer token刷新机制详细解读
  • 第十三章 Ant与Jenkins的集成
  • 通义万相正式开源Wan2.2-Animate动作生成模型
  • 课后作业-2025-09-21
  • 比 IDEA 容器自动化部署更轻量便捷的工具
  • 知识图谱技术对自然语言处理深层语义分析的影响与启示研究
  • Javase 高级技术 —— 01 反射
  • Linux 孤儿进程与进程的优先级和切换和调度
  • QML学习笔记(七)QML的基本数据类型
  • 基于51单片机电子钟闹钟12/24小时制LCD显示
  • 小程序开发全解析:从结构到API
  • 异步方法在C#中的使用