CFS的覆灭,Linux新调度器EEVDF详解

本文主要总结了EEVDF论文和Linux内核实现中关键逻辑的推导,着重强调代码逻辑和论文公式之间的关系,它又长又全,像今天的汤圆又大又圆:D

Warn:多行的公式编号渲染有点问题,当存在多行公式时,仅对最后一条式子编号。

简 介

CFS 调度器强调公平,只提供权重一个变量(nice)用于控制任务之间运行时间分配的比例,不提供对特定任务时延层面的保障。这导致服务质量保障(Quality of Service,QoS)很难实现,传统手段是通过为不同任务设置标签,在调度关键逻辑(唤醒、抢占、负载均衡)对打了不同标签的任务进行特判处理,导致调度逻辑充满了特判和兜底。

为了将时延考虑在内,新调度器 EEVDF 在保障时间分配公平的同时,引入代表任务“应得时间”和“已得时间”的差异值 lag 作为关键指标,只有“应得时间”大于等于“已得时间”,任务才有资格(称eligible)被挑选;进一步地,在有资格被挑选的任务中,选择任务完成时限(deadline)最近的任务运行,即所谓 Earliest Eligible Virtual Deadline First,缩写为 EEVDF。

EEVDF 论文

本节主要梳理论文中的关键概念,与Linux实现息息相关的部分:

Vt:系统虚拟时间

任务 i 在 [ t 1 , t 2 ] [t_1,t_2] [t1,t2] 时间段,应得运行时间为:
S ⁡ ( t 1 , t 2 ) = ∫ t 1 t 2 w i W (1) \operatorname{S}(t_1, t_2) = \int_{t_1}^{t_2} \frac{w_i}{W} \tag{1} S(t1,t2)=t1t2Wwi(1)
其中:

  1. w i w_i wi 是任务 i 的权重。
  2. W W W 是系统中所有活跃任务权重的累加;

令系统虚拟时间为:
V t = ∫ 0 t 1 W (2) V_t = \int_{0}^{t} \frac{1}{W} \tag{2} Vt=0tW1(2)
则式(1)又可以写作:
S ⁡ ( t 1 , t 2 ) = ∫ t 1 t 2 w i W = w i ∫ t 1 t 2 1 W = w i ( V t 2 − V t 1 ) (3) \begin{align} \operatorname{S}(t_1, t_2) & = \int_{t_1}^{t_2} \frac{w_i}{W} \\ & = w_i \int_{t_1}^{t_2} \frac{1}{W} \\ & = w_i(V_{t_2}-V_{t_1}) \end{align} \tag{3} S(t1,t2)=t1t2Wwi=wit1t2W1=wi(Vt2Vt1)(3)

ve:eligible time

任务 i 请求的 eligible time 被定义为:任务 i 的应得时间,和 i 在发起该请求前已得时间相等的那一刻,记该虚拟时间为 eligible time。当系统虚拟时间 V t V_t Vt 大于等于 V e V_e Ve,称任务 i 的请求 eligible,即任务 i 有资格作为上 CPU 运行的候选任务。

记任务 i 在 [ t 1 , t 2 ] [t_1,t_2] [t1,t2] 时间段实际获得的运行时间为 s(t1,t2),根据 eligible time 的定义,有:
S ⁡ ( t 0 , e ) = s ⁡ ( t 0 , t ) (4) \begin{array}{c} \operatorname{S}(t_0, e) = \operatorname{s}(t_0, t) \tag{4} \end{array} S(t0,e)=s(t0,t)(4)
其中 t 0 t_0 t0 是任务 i 开始活跃的时间, t t t 为任务 i 发起新请求的时间,结合公式(3),可推出 eligible time 的计算方式:
S ⁡ ( t 0 , e ) = s ⁡ ( t 0 , t ) = > w i ( V e − V t 0 ) = s ⁡ ( t 0 , t ) = > V e = V t 0 + s ( t 0 , t ) w i (5) \begin{array}{c} \operatorname{S}(t_0, e) = \operatorname{s}(t_0, t)\\ = > w_i(V_e-V_{t_0}) = \operatorname{s}(t_0, t) \\ = > V_e = V_{t_0} + \frac{s(t_0, t)}{w_i} \tag{5} \end{array} S(t0,e)=s(t0,t)=>wi(VeVt0)=s(t0,t)=>Ve=Vt0+wis(t0,t)(5)

vd:deadline time

任务 i 请求的 deadline time 被定义为:任务 i 在虚拟时间间隔 [ V e , V d ] [V_e,V_d] [Ve,Vd] 之间可得的运行时间,等于请求的长度 r。有:
S ⁡ ( e , d ) = r \begin{array}{c} \operatorname{S}(e, d) = r \end{array} S(e,d)=r
结合公式(3),可推出 deadline time 的计算方式如下:
S ⁡ ( e , d ) = r = > w i ( V d − V e ) = r = > V d = V e + r w i (6) \begin{array}{c} \operatorname{S}(e, d) = r \\ = > w_i(V_d-V_e) = r \\ = > V_d = V_e + \frac{r}{w_i} \tag{6} \end{array} S(e,d)=r=>wi(VdVe)=r=>Vd=Ve+wir(6)

ve 和 vd 递推公式

根据式(5)和式(6),对于任务的多个请求,有以下递推公式:
V e 1 = V t 0 V d 1 = V t 0 + r w i . . . V e k = V d k − 1 V d k = V e k + r w i (7) \begin{array}{c} V_e^1 = V_{t_0} \\ V_d^1 = V_{t_0} + \frac{r}{w_i} \\ ... \\ V_e^k = V_d^{k-1} \\ V_d^k = V_e^{k} + \frac{r}{w_i} \tag{7} \end{array} Ve1=Vt0Vd1=Vt0+wir...Vek=Vdk1Vdk=Vek+wir(7)

ve 和 vd 推算例子

假设:

  1. c1 和 c2 的权重相等,有 w 1 = w 2 = 2 w_1 = w_2 = 2 w1=w2=2
  2. c1 在 t=0 加入竞争,c2 在 t=1 加入竞争;
  3. c1 的请求长度 r 1 = 2 r_1=2 r1=2,c2 的请求长度 r 2 = 1 r_2=1 r2=1
    在这里插入图片描述

推算详解:
在这里插入图片描述

lag:为了公平

任务 i 应得时间和已得时间不总是相等的,两者的差值称作 lag:
l a g i ( t ) = S i ( t 0 , t ) − s i ( t 0 , t ) (8) \begin{array}{c} lag_i(t) = S_i(t_0,t) - s_i(t_0,t) \tag{8} \end{array} lagi(t)=Si(t0,t)si(t0,t)(8)
当:

  1. 当系统虚拟时间和任务的 eligible time 相等,即 V t = V e V_t = V_e Vt=Ve,则任务的 lag 为 0。
  2. S i S_i Si 大于 s i s_i si,说明 [ t 0 , t ] [t_0,t] [t0,t] 时间段内,任务 i 已得时间比应得时间少,此时 lag 为正,有 V t > V e V_t > V_e Vt>Ve,则任务 i 一旦有新的请求,该请求会立马进入 eligible 状态。
  3. S i S_i Si 小于 s i s_i si,说明 [ t 0 , t ] [t_0,t] [t0,t] 时间段内,任务 i 已得时间比应得时间多,此时 lag 为负,有 V t < V e V_t < V_e Vt<Ve,则任务 i 需要等到系统虚拟时间 V t V_t Vt 增长到 V e V_e Ve,才进入 eligible 状态,为其它任务提供“catch up”的机会。

任务可能在 lag 不为 0 的场景下加入或者退出竞争(两种行为是对称的,以下只讨论退出竞争场景)。为了保证公平,EEVDF 算法通过将退出竞争任务的 lag(该任务透支的,或残余的时间)按各自权重大小,反映到剩余活跃的任务上。以上逻辑通过更新系统虚拟时间 V t V_t Vt 和其它任务的 lag 实现。推导过程如下:

系统虚拟时间 Vt 更新

假设任务 a 在时间 t 退出竞争,取无限小时间间隔 δ δ δ,令 t + t^+ t+ t + δ t+δ t+δ,则系统中其它任务 i 在时间 [ t 0 , t + ] [t_0,t^+] [t0,t+] 内可分配的运行时间可表示为:
S i ( t 0 , t + ) = ( t − t 0 − s a ( t 0 , t ) ) w i W (9) \begin{array}{c} S_i(t_0,t^+) = (t - t_0 -s_a(t_0,t)) \frac{w_i}{W} \tag{9} \end{array} Si(t0,t+)=(tt0sa(t0,t))Wwi(9)
其中 W W W 为任务 a 退出竞争后系统中所有活跃任务权重的累加。将式(10)中 s a ( t 0 , t ) s_a(t_0, t) sa(t0,t) 替换由任务 a 的 lag 表示,由此推出任务 a 在退出竞争后系统虚拟时间 V t V_t Vt 会被更新为:
∵ l a g a ( t ) = ( t − t 0 ) w a W + w a − s a ( t 0 , t ) ∴ s a ( t 0 , t ) = ( t − t 0 ) w a W + w a − l a g a ( t ) ∴ S i ( t 0 , t + ) = [ t − t 0 − ( t − t 0 ) w a W + w a + l a g a ( t ) ] w i W = [ ( t − t 0 ) W W + w a + l a g a ( t ) ] w i W = ( t − t 0 ) w i W + w a + l a g a ( t ) w i W = w i ( V t − V t 0 ) + l a g a ( t ) w i W ∵ S i ( t 0 , t + ) = w i ( V t + − V t 0 ) = w i ( V t − V t 0 ) + l a g a ( t ) w i W ∴ V t + = V t + l a g a ( t ) W \begin{align} \because lag_a(t) & = (t - t_0) \frac{w_a}{W + w_a} - s_a(t_0,t) \\ \therefore s_a(t_0,t) & = (t - t_0) \frac{w_a}{W + w_a} - lag_a(t) \\ \therefore S_i(t_0,t^+) & = [t - t_0 - (t - t_0) \frac{w_a}{W + w_a} + lag_a(t)] \frac{w_i}{W} \\ & = [(t - t_0) \frac{W}{W + w_a} + lag_a(t)] \frac{w_i}{W} \\ & = (t - t_0) \frac{w_i}{W + w_a} + lag_a(t) \frac{w_i}{W} \\ & = w_i(V_t - V_{t_0}) + lag_a(t) \frac{w_i}{W} \\ \because S_i(t_0,t^+) & = w_i(V_{t^+} - V_{t_0}) = w_i(V_t - V_{t_0}) + lag_a(t) \frac{w_i}{W} \\ \therefore V_{t^+} & = V_t + \frac{lag_a(t)}{W} \tag{10} \end{align} laga(t)sa(t0,t)Si(t0,t+)Si(t0,t+)Vt+=(tt0)W+wawasa(t0,t)=(tt0)W+wawalaga(t)=[tt0(tt0)W+wawa+laga(t)]Wwi=[(tt0)W+waW+laga(t)]Wwi=(tt0)W+wawi+laga(t)Wwi=wi(VtVt0)+laga(t)Wwi=wi(Vt+Vt0)=wi(VtVt0)+laga(t)Wwi=Vt+Wlaga(t)(10)

任务 lag 更新

代入式(10),任务 a 在退出竞争后其它任务的 lag 会被更新为:
l a g i ( t + ) = w i ( V t + − V t 0 ) − s i ( t 0 , t + ) = w i ( V t + l a g a ( t ) W − V t 0 ) − s i ( t 0 , t + ) = w i ( V t − V t 0 ) − s i ( t 0 , t + ) + w i l a g a ( t ) W ∵ s i ( t 0 , t + ) ⟶ s i ( t 0 , t ) ∴ l a g i ( t + ) = l a g i ( t ) + w i l a g a ( t ) W \begin{align} lag_i(t^+) &= w_i(V_{t^+} - V_{t_0}) - s_i(t_0,t^+) \\ & = w_i(V_t + \frac{lag_a(t)}{W} - V_{t_0}) - s_i(t_0,t^+) \\ & = w_i(V_t - V_{t_0}) - s_i(t_0,t^+) + w_i \frac{lag_a(t)}{W} \\ \because s_i(t_0,t^+) &\longrightarrow s_i(t_0,t) \\ \therefore lag_i(t^+) &= lag_i(t) + w_i \frac{lag_a(t)}{W} \tag{11} \end{align} lagi(t+)si(t0,t+)lagi(t+)=wi(Vt+Vt0)si(t0,t+)=wi(Vt+Wlaga(t)Vt0)si(t0,t+)=wi(VtVt0)si(t0,t+)+wiWlaga(t)si(t0,t)=lagi(t)+wiWlaga(t)(11)
可以发现非常自然地,退出竞争任务 a 的 l a g a ( t ) lag_a(t) laga(t) 按剩余任务各自的权重大小,均匀地作用到其它任务的 lag 上。

EEVDF调度类原始提交

以下梳理 Linux 引入 EEVDF 调度器的初始提交中关键的三个patch:

sched/fair: Add cfs_rq::avg_vruntime

该提交引入 cfs_rq→avg_vruntimecfs_rq→avg_load,目的是为了方便计算系统虚拟时间 V t V_t Vt(该变量在在代码中叫做avg_vruntime,注意区分 cfs_rq→avg_vruntimeavg_vruntime)。系统虚拟时间的计算方式从论文的以下引理开始推导:

Lemma 2 At any moment of time,the sum of the lags of all active client is zero.

有:
∑ l a g i ( t ) = 0 (12) \begin{array}{c} \sum lag_i(t) = 0 \tag{12} \end{array} lagi(t)=0(12)
将 lag 的计算公式(8)代入后可以得到:
∑ l a g i ( t ) = ∑ [ S i ( t 0 , t ) − s i ( t 0 , t ) ] = 0 (13) \begin{array}{c} \sum lag_i(t) = \sum [S_i(t_0,t) - s_i(t_0,t)] = 0 \tag{13} \end{array} lagi(t)=[Si(t0,t)si(t0,t)]=0(13)
原 CFS 调度器中已经记录了调度单元实际运行的时间,即se->vruntime,记 se->vruntime V t i V_{t_i} Vti,则有:
∑ l a g i ( t ) = ∑ [ S i ( t 0 , t ) − s i ( t 0 , t ) ] = ∑ [ S i ( t 0 , t ) − S i ( t 0 , t i ) ] = ∑ [ S i ( t 0 , t ) − S i ( t 0 , t i ) ] = ∑ [ w i ( V t − V t 0 ) − w i ( V t i − V t 0 ) ] = ∑ [ w i ( V t − V t i ) ] = ∑ w i V t − ∑ w i V t i \begin{align} \sum lag_i(t) & = \sum [S_i(t_0,t) - s_i(t_0,t)] \\ & = \sum [S_i(t_0,t) - S_i(t_0,t_i)] \\ & = \sum [S_i(t_0,t) - S_i(t_0,t_i)] \\ & = \sum [w_i(V_t - V_{t_0}) - w_i(V_{t_i} - V_{t_0})] \\ & = \sum [w_i(V_t - V_{t_i})] \\ & = \sum w_iV_t - \sum w_iV_{t_i} \tag{14}\\ \end{align} lagi(t)=[Si(t0,t)si(t0,t)]=[Si(t0,t)Si(t0,ti)]=[Si(t0,t)Si(t0,ti)]=[wi(VtVt0)wi(VtiVt0)]=[wi(VtVti)]=wiVtwiVti(14)
又因式(14)=0,移项之后, V t V_t Vt 可以表示如下:
V t = ∑ w i V t i W \begin{align} V_t = \frac{\sum w_iV_{t_i}}{W} \tag{15} \end{align} Vt=WwiVti(15)
V t V_t Vt 可以表示为所有活跃任务 vruntime 按权重占比的加权和。但在具体实现上,任务 vruntime 的数据类型是 u64,进行乘法运算容易导致溢出。记 cfs->min_vruntime V 0 V_0 V0,进行以下等价替换:
V t i = ( V t i − V 0 ) + V 0 \begin{align} V_{t_i} = (V_{t_i} - V_0) + V_0 \tag{16} \end{align} Vti=(VtiV0)+V0(16)
将式(16)代入式(15),则有:
V t = ∑ w i V t i W = ∑ w i [ ( V t i − V 0 ) + V 0 ] W = ∑ w i ( V t i − V 0 ) + V 0 ∑ w i W = ∑ w i ( V t i − V 0 ) W + V 0 \begin{align} V_t & = \frac{\sum w_iV_{t_i}}{W} \\ & = \frac{\sum w_i[ (V_{t_i} - V_0) + V_0]}{W} \\ & = \frac{\sum w_i(V_{t_i} - V_0) + V_0\sum w_i}{W} \\ & = \frac{\sum w_i (V_{t_i} - V_0)}{W} + V_0 \tag{17} \end{align} Vt=WwiVti=Wwi[(VtiV0)+V0]=Wwi(VtiV0)+V0wi=Wwi(VtiV0)+V0(17)
代码实现中,式(17)被切分为3个部分,分别由三个变量追踪,第一个是已有的变量,后两个是本次提交新引入的变量:

  1. cfs->min_vruntime:式中 V 0 V_0 V0
  2. cfs_rq→avg_vruntime:式中 ∑ w i ( V t i − V 0 ) \sum w_i (V_{t_i} - V_0) wi(VtiV0)
  3. cfs_rq→avg_load:式中 W W W

具体实现中,通过 avg_vruntime() 函数获取当前系统虚拟时间 V t V_t Vt,具体计算逻辑如下:

|- avg_vruntime|- s64 avg = cfs_rq->avg_vruntime;|- long load = cfs_rq->avg_load;|- unsigned long weight = scale_load_down(curr->load.weight);// cfs_rq->avg_vruntime 累加 curr se 的贡献,其中 entity_key 用于计算 v_i - v0 |- avg += entity_key(cfs_rq, curr) * weight|- load += weight;// 返回 V|- return cfs_rq->min_vruntime + avg / load;

以下场景需要同步对新引入变量进行更新:

// 1. 更新 cfs_rq->min_vruntime 场景,同步更新 cfs_rq->avg_vruntime
|- [update_curr|dequeue_task_fair]// 更新 cfs_rq->min_vruntime|- update_min_vruntime -> __update_min_vruntime// 更新 cfs_rq->avg_vruntime,以下时序关系为 v0 -> v0' -> vi,min_vruntime 将从 v0 更新为 v0'// avg_vruntime_new = \Sum (v_i - v0') * w_i//                  = \Sum ((v_i - v0) - (v0' - v0)) * w_i//                  = \Sum (v_i - v0) * w_i - \Sum (v0' - v0) * w_i//                  = avg_vruntime_old - (v0' - v0) * avg_load|- avg_vruntime_update|- cfs_rq->avg_vruntime -= cfs_rq->avg_load * delta;// 2. 任务入队出队场景,更新 cfs_rq->avg_vruntime 和 cfs_rq->avg_load
|- avg_vruntime_add|- cfs_rq->avg_vruntime += entity_key(cfs_rq, se) * weight;|- cfs_rq->avg_load += weight;
sched/fair: Add lag based placement

关于任务持 lag 重新加入竞争场景,为了保证相对公平,论文提供了多种实现策略,当前提交实现了以下两种:

  1. V t V_t Vt 随加入和退出调度实体的 lag 进行更新(参考式10):该策略适合希望在任务活动的多个阶段保持公平的系统。
  2. 调度实体的 lag 在退出竞争后清 0,重新加入竞争不处理 lag:该策略适合任务活动的触发是独立事件的系统。

策略可通过调度特性 sched_feat PLACE_LAG 的值进行控制,当前默认采用策略 1。上文我们已经推导了任务 a 退出竞争时, V t V_t Vt 的更新公式——式(10),也推导了其它任务 a 在 V t V_t Vt 更新后lag值的变化。同理可推导出任务 a 加入竞争后, V t V_t Vt 的更新公式:
V t + = V t − l a g a ( t ) W + w a \begin{align} V_{t^+} & = V_t - \frac{lag_a(t)}{W + w_a} \tag{18} \end{align} Vt+=VtW+walaga(t)(18)
当前提交为了追踪每个调度实体的 lag,为 sched_entity 引入了新成员 se->vlag(以下记作 v l ( t ) vl(t) vl(t)),原 lag 计算公式为 w i ( V t − V t i ) w_i(V_t - V_{t_i}) wi(VtVti),为了防止到处都是 w i w_i wise->vlag 仅记录公式中 vruntime 的差值部分,即 v l ( t ) = V t − V t i vl(t) = V_t - V_{t_i} vl(t)=VtVti。则式(18)可改写为:
V t + = V t − w a ∗ v l a ( t ) W + w a \begin{align} V_{t^+} & = V_t - \frac{w_a * vl_a(t)}{W + w_a} \tag{19} \end{align} Vt+=VtW+wawavla(t)(19)
V t V_t Vt 更新后,代入式(19),在加入竞争后,任务 a 的 vlag 将被更新为:
v l a ( t + ) = V t + − V t i = V t − V t i − w a ∗ v l a ( t ) W + w a = v l a ( t ) − w a ∗ v l a ( t ) W + w a \begin{align} vl_a(t^+) & = V_{t^+} - V_{t_i} \\ & = V_t - V_{t_i} - \frac{w_a * vl_a(t)}{W + w_a} \\ & = vl_a(t) - \frac{w_a * vl_a(t)}{W + w_a} \tag{20} \end{align} vla(t+)=Vt+Vti=VtVtiW+wawavla(t)=vla(t)W+wawavla(t)(20)
可以发现 v l a ( t + ) vl_a(t^+) vla(t+) 严格小于 v l a ( t ) vl_a(t) vla(t),这会导致任务 a 在频繁加入退出竞争场景中 v l a ( t ) vl_a(t) vla(t) 急速衰减,策略一会退化为策略二。为了保证任务 a 的 v l a ( t ) vl_a(t) vla(t) 在加入竞争前后保持不变,我们需要将 v l a ( t ) vl_a(t) vla(t) 提前放大,使其衰减之后恰好等于 v l a ( t ) vl_a(t) vla(t)。通过变换式(20),可知 v l a ( t ) vl_a(t) vla(t) 应当放大为:
∵ v l a ( t + ) = v l a ( t ) − w a ∗ v l a ( t ) W + w a ∴ v l a ( t + ) = [ ( W + w a ) v l a ( t ) − w a ∗ v l a ( t ) ] W + w a ∴ v l a ( t + ) = W ∗ v l a ( t ) W + w a ∴ W ∗ v l a ( t ) = ( W + w a ) v l a ( t + ) ∴ v l a ( t ) = ( W + w a ) v l a ( t + ) W (21) \begin{array}{c} \because vl_a(t^+) = vl_a(t) - \frac{w_a * vl_a(t)}{W + w_a} \\ \therefore vl_a(t^+) = \frac{[(W + w_a)vl_a(t) - w_a * vl_a(t)]}{W + w_a} \\ \therefore vl_a(t^+) = \frac{W*vl_a(t)}{W + w_a} \\ \therefore W*vl_a(t) = (W + w_a)vl_a(t^+) \\ \therefore vl_a(t) = \frac{(W + w_a)vl_a(t^+)}{W} \tag{21} \end{array} vla(t+)=vla(t)W+wawavla(t)vla(t+)=W+wa[(W+wa)vla(t)wavla(t)]vla(t+)=W+waWvla(t)Wvla(t)=(W+wa)vla(t+)vla(t)=W(W+wa)vla(t+)(21)
在加入竞争前(place_entity函数中),通过赋值 V t i = V t − ( W + w a ) v l a ( t ) W V_{t_i} = V_t - \frac{(W + w_a)vl_a(t)}{W} Vti=VtW(W+wa)vla(t) ,使原始 vlag v l a ( t ) vl_a(t) vla(t) 被放大为 ( W + w a ) v l a ( t ) W \frac{(W + w_a)vl_a(t)}{W} W(W+wa)vla(t),通过将 ( W + w a ) v l a ( t ) W \frac{(W + w_a)vl_a(t)}{W} W(W+wa)vla(t) 代入式(20),可得 v l a ( t + ) = v l a ( t ) vl_a(t^+) =vl_a(t) vla(t+)=vla(t),实现加入竞争前后vlag保持不变的效果。

相关逻辑如下

|- place_entity|- u64 vruntime = avg_vruntime(cfs_rq);|- s64 lag = 0; // 用于保存放大后的 vlag|- if (sched_feat(PLACE_LAG) && cfs_rq->nr_running) // 策略 1 分支lag = se->vlag; // 原始 vlagload = cfs_rq->avg_load; // Wlag *= load + scale_load_down(se->load.weight); // vlag * (W + wi)lag = div_s64(lag, load); // vlag * (W + wi) / W|- se->vruntime = vruntime - lag; // 使后续 vruntime - se->vruntime 可以拿到放大后的 vlag,即 vlag * (W + wi) / W

以下场景需要同步对vlag进行修改:

// 1. 调度实体离开运行队列,保存lag(策略一需求)
|- dequeue_entity|- update_entity_lag(cfs_rq, se);// 2.更改调度实体的权重,因为lag需要保持不变,vlag需要同步做缩放
|- reweight_entity|- se->vlag = div_s64(se->vlag * old_weight, weight);
sched/fair: Implement an EEVDF-like scheduling policy

Linux 原调度器 CFS 可调参数是调度实体的权重值,用于控制调度实体之间运行时间的分配比例;当引入另一个用于控制时延的参数时,系统采用类似于 WF2Q 或者 EEVDF 的调度器会更合适。具体而言,EEVDF 调度器有以下两个参数:

  1. weight:权重值,保留 CFS 调度器的实现,由 nice 值控制。
  2. slice length:请求长度,用于计算 deadline。

通过为任务设置一个小的 slice,相应的该任务请求的 deadline 也会提前,因为 EEVDF 会选择 eligible 的任务中 deadline 最小的任务运行,所以该任务可以更早地被选择运行。

当前提交改动包括:

  1. 为调度实体引入 slice 用于计算 deadline,并新增维护该 deadline 的相关逻辑。
  2. 实现 EEVDF 选调度实体的逻辑 pick_eevdf,添加任务抢占相关逻辑。

数据结构的修改:

struct sched_entity {
// 当前调度实体的 deadline
+       u64                             deadline;
// EEVDF 为了在红黑树内部快速查找目标调度实体(eligible 中 deadline 最小的),
// 引入 min_deadline 用于记录以调度实体为根的子树中最小的 deadline 值
+       u64                             min_deadline;
// 当前调度实体的请求长度
+       u64                             slice;

为保证 se->min_deadline 保存的是子树中最小的 deadline,新增回调函数(min_deadline_update()),在红黑树节点插入删除节点(__enqueue_entity/__dequeue_entity)时被调用,该回调函数顺着红黑树递归更新红黑树中调度实体的 min_deadline。

新增函数 entity_eligible 判断调度实体是否 eligible,根据上文的推断,调度实体 eligible 和 vlag 大于等于 0 是等价的,即 V t − V t i > = 0 V_t - V_{t_i} >= 0 VtVti>=0,回忆式(17),
V t = ∑ w i ( V t i − V 0 ) W + V 0 \begin{align} V_t = \frac{\sum w_i (V_{t_i} - V_0)}{W} + V_0 \end{align} Vt=Wwi(VtiV0)+V0
则 eligible 等价于令上式大于等于 V t i V_{t_i} Vti (即se->vruntime),为不丢失精度,进行移项去除除号,实现如下:

|- entity_eligible|- s64 avg = cfs_rq->avg_vruntime; // \sum w_i (V_{t_i} - V_0)|- long load = cfs_rq->avg_load; // W|- if (curr && curr->on_rq)avg += entity_key(cfs_rq, curr) * weight; // avg 考虑当前调度实体load += weight; // load 考虑当前调度实体|- return avg >= entity_key(cfs_rq, se) * load; // \sum w_i (V_{t_i} - V_0) >= (V_t - V_0)* W

调度实体的 deadline 在 update_curr 时被更新:

|- update_curr -> update_deadline(cfs_rq, curr);// se->slice 可由 /sys/kernel/debug/sched/base_slice_ns 控制,当前该值对于每个任务是一致的// vd_i = ve_i + r_i / w_i,calc_delta_fair|- se->deadline = se->vruntime + calc_delta_fair(se->slice, se);

pick_eevdf 用于选出红黑树中处于 eligible 状态中 deadline 最小的那个调度实体:

|- pick_eevdf// 如果当前任务没有入队,或者当前任务不再 eligible,当前任务不作为备选|- if (curr && (!curr->on_rq || !entity_eligible(cfs_rq, curr))) curr = NULL;// 从红黑树根节点开始遍历while (node) {struct sched_entity *se = __node_2_se(node);// 红黑树以 vruntime 进行排序,所以右子树的 vruntime 一定比当前节点的 vruntime 大// 而 eligible 条件是 V - vruntime > 0,如果当前节点都不 eligible,右子树必定不 eligible// 应当在左子树进行查找if (!entity_eligible(cfs_rq, se)) {node = node->rb_left;continue;}// best 变量用于追踪 eligible 节点中 deadline 最小的节点// 如果搜索过程中发现有 deadline 更小的,赋值|- if (!best || deadline_gt(deadline, best, se))best = se;// 如果该节点的 deadline 等于子树中 deadline 最小的值,说明其本身是子树中 deadline 最小的,结束查找if (best->deadline == best->min_deadline) break;// 最小的 deadline 在左子树,在左子树继续搜索if (node->rb_left && __node_2_se(node->rb_left)->min_deadline == se->min_deadline)node = node->rb_left;continue;// 最小的 deadline 在右子树,在右子树继续搜索(当前有bug,在优化一节会修正)node = node->rb_right;}// 如果没搜索到,或者 curr 的 deadline 比 best 要更小,选择 curr|- if (!best || (curr && deadline_gt(deadline, best, curr))) best = curr;|- return best;

关于任务抢占行为逻辑增加:

// 1. tick中抢占检测由slice的完成驱动,任务的slice消耗完毕后,设置重调度标记
|- entity_tick -> update_curr(cfs_rq);|- update_deadline(cfs_rq, curr)// slice 还有剩余,无需更新(递推)下一个slice的结束时间,直接返回|- if ((s64)(se->vruntime - se->deadline) < 0) return;// 更新 deadline|- se->deadline = se->vruntime + calc_delta_fair(se->slice, se);// 运行队列存在其它任务,设置重调度标记|- if (cfs_rq->nr_running > 1) resched_curr(rq_of(cfs_rq));// 2. 唤醒中抢占检测由 deadline 驱动
|- ttwu_do_wakeup -> check_preempt_curr(rq, p, wake_flags) -> check_preempt_wakeup// 如果被唤醒的任务 p 是 RBT 的 eligible 任务中 deadline 最早的,设置重调度标记|- if (pick_eevdf(cfs_rq) == pse) resched_curr(rq);

修正了 update_entity_lag() 的实现逻辑, l a g i = S − s i = w i ∗ ( V − v i ) lag_i = S - s_i = w_i * (V - v_i) lagi=Ssi=wi(Vvi),因为系统虚拟时间会随调度实体的加入/退出/重新设置权重值而前后移动,可能会导致 lag 比原本的要小。根据论文的 Theorem 1,在稳定系统中每个任务的 lag 有上下界:
− r m a x < l a g < m a x ( r m a x , q ) (22) -r_{max} < lag < max(r_{max}, q) \tag{22} rmax<lag<max(rmax,q)(22)
式中 r m a x r_{max} rmax 是任务发起请求中最大的请求长度,实现中取做两倍 se->slice q q q 代表 time quantum 的大小,实现中取 TICK_NSEC(系统计时粒度)。逻辑更改如下:

|- update_entity_lag|- lag = avg_vruntime(cfs_rq) - se->vruntime; // 获取 lag|- limit = max_t(u64, 2*se->slice, TICK_NSEC); // 确认上下界|- se->vlag = clamp(lag, -limit, limit); // 将 lag 界定在上下界中

EEVDF调度类相关优化

以下简单介绍两个相关优化:

sched/eevdf: Fix vruntime adjustment on reweight

当任务的权重从 w 更新为 w’,等价于 w 权重任务的出队,以及 w’ 权重任务的入队。在提交 sched/fair: Add lag based placement 中我们得知任务的出入队应当保证 vlag 不变,为了满足该需求,任务权重的修改需要同步修改 vruntime 和 deadline。

推导过程在代码注释中相当详尽,这里仅做概括:

  1. 通过归谬法证明:假设任务的 vruntime 在 reweight 前后不需要修正,根据 reweight 前后 vlag 应该保持不变这个条件,会推导出 w=w’,与假设相悖,因此 reweight 前后任务的 vruntime 需要进行修正。
  2. 根据 reweight 前后 vlag 应该保持不变这个条件,可以推导出系统虚拟时间在 reweight 前后应当保持一致。
  3. 根据 2 的结论,推导出任务 vruntime 的更新公式。
  4. 通过替换 d’ = v’ + r/w’ 公式中的 v’ 和 r,并结合结论 2,推导出 deadline 的更新方式。
sched/eevdf: Sort the rbtree by virtual deadline

在提交 sched/fair: Implement an EEVDF-like scheduling policy 中引入了 pick_eevdf() 函数用于查找 RBT 中 eligible 的调度实体中 deadline 最小的调度实体。但因为遍历 RBT 过程中,在以找到 min_deadline 为目标挑选左右子树的过程中,可能导致返回的调度实体并不 eligible。

提交 sched/eevdf: Fix pick_eevdf() 尝试对其进行修复,但需要多次查找,引入了较高的时间复杂度。

当前提交和 sched/eevdf: O(1) fastpath for task selection 通过修改数据结构,使 cfs_rq 将任务 deadline 作为红黑树的键,而引入 min_vruntime 记录子树 vruntime 最小的值。跟初始方案对比,调换了 cfs_rq 中 deadline 和 vruntime 的排序方式。相应地 pick_eevdf() 被修正为:

|- pick_eevdfstruct sched_entity *se = __pick_first_entity(cfs_rq); // deadline 最小的任务// 如果 deadline 最小的任务 eligible,获取成功|- if (se && entity_eligible(cfs_rq, se))best = se;goto found;// 堆搜索|- while (node) {|- struct rb_node *left = node->rb_left;// 如果左子树存在 eligible 节点,因为左子树的 deadline 总是更小,应当在左子树进行查找|- if (left && vruntime_eligible(cfs_rq, __node_2_se(left)->min_vruntime))node = left;continue;// 走到这,左子树为空或者不包含 eligible 节点,当前节点是 deadline 最小的// 如果当前节点 eligible,应当选择当前节点|- if (entity_eligible(cfs_rq, se))best = se;break;// 左子树和当前节点都不符合要求,在右子树继续搜索|- node = node->rb_right;// 如果没搜索到,或者 curr 的 deadline 比 best 要更小,选择 curr|- if (!best || (curr && deadline_gt(deadline, best, curr))) best = curr;|- return best;

在这里插入图片描述

ref

  1. Earliest Eligible Virtual Deadline First : A Flexible and Accurate Mechanism for Proportional Share Resource Allocation
  2. An EEVDF CPU scheduler for Linux
  3. Linux 核心設計: Scheduler(5): EEVDF 排程器
  4. Linux 核心專題: CPU 排程器研究

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/703238.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是媒体发稿?发稿媒体分类及发稿流程

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 媒体发稿是一种企业推广和宣传的手段&#xff0c;通过媒体渠道传递企业信息和形象。 媒体发稿的含义在于&#xff0c;当企业有新闻、事件或其他消息需要对外公布时&#xff0c;可以选择…

第三节:Vben Admin登录对接后端login接口

系列文章目录 第一节&#xff1a;Vben Admin介绍和初次运行 第二节&#xff1a;Vben Admin 登录逻辑梳理和对接后端准备 文章目录 系列文章目录前言一、Flask项目介绍二、使用步骤1.User模型创建2.迁移模型3. Token创建4. 编写蓝图5. 注册蓝图 三. 测试登录总结 前言 上一节&…

【基于Ubuntu20.04的Autoware.universe安装过程】方案一:虚拟机 | 详细记录 | Vmware | 全过程图文 by.Akaxi

目录 一、Autoware.universe背景 二、虚拟机配置 三、Ubuntu20.04安装 四、GPU显卡安装 五、ROS2-Galactic安装 六、ROS2-dev-tools安装 七、rmw-implementation安装 八、pacmod安装 九、autoware-core安装 十、autoware universe dependencies安装 十一、安装pre-c…

[ai笔记12] chatGPT技术体系梳理+本质探寻

欢迎来到文思源想的ai空间&#xff0c;这是技术老兵重学ai以及成长思考的第12篇分享&#xff01; 这周时间看了两本书&#xff0c;一本是大神斯蒂芬沃尔弗拉姆学的《这就是ChatGPT》,另外一本则是腾讯云生态解决方案高级架构师宋立恒所写的《AI制胜机器学习极简入门》&#xf…

2024最新水果软件FL Studio21版本介绍与功能对比

FL Studio21前身为Fruity Loops&#xff0c;是一款由Image-Line公司开发的数字音频工作站&#xff08;DAW&#xff09;。它广泛用于音乐制作、编曲、混音和录音等领域。随着软件的不断更新迭代&#xff0c;FL Studio推出了多个版本&#xff0c;以满足不同用户的需求。 FL Studi…

数字化转型导师坚鹏:县区级政府数字化转型案例研究

县区级政府数字化转型案例研究 课程背景&#xff1a; 很多县区级政府存在以下问题&#xff1a; 不清楚县区级政府数字化转型的政务服务类成功案例 不清楚县区级政府数字化转型的社会管理类成功案例 不清楚县区级政府数字化转型的智慧城市类成功案例 课程特色&#xff…

【android】android studio生成aar包并在其他工程引用aar包(类/函数/activity)

android studio生成aar包并在其他工程引用aar包 arr引用和jar引用的区别1.ARR实现简单的JAVA/Kotlin类和函数调用过程1.1.新建需要打包成AAR的模块类1.2.新建需要调用的类1.3 创建 AAR 文件&#xff1a;1.4 AAR文件使用1.5 函数调用 2.实现AAR中activity的调用过程2.1 **特别说…

数字化转型导师坚鹏:地市级政府数字化转型案例研究

地市级政府数字化转型案例研究 课程背景&#xff1a; 很多地市级政府存在以下问题&#xff1a; 不清楚地市级政府数字化转型的政务服务类成功案例 不清楚地市级政府数字化转型的社会管理类成功案例 不清楚地市级政府数字化转型的智慧城市类成功案例 课程特色&#xff…

从软硬件以及常见框架思考高并发设计

目录 文章简介 扩展方式 横向扩展 纵向扩展 站在软件的层面上看 站在硬件的层面上看 站在经典的单机服务框架上看 性能提升的思考方向 可用性提升的思考方向 扩展性提升的思考方向 文章简介 先从整体&#xff0c;体系认识&#xff0c;理解高并发的策略&#xff0c;方…

【DAY04 软考中级备考笔记】数据结构基本结构和算法

数据结构基本结构和算法 2月25日 – 天气&#xff1a;晴 周六玩了一天&#xff0c;周天学习。 1. 什么是数据结构 数据结构研究的内容是一下两点&#xff1a; 如何使用程序代码把现实世界的问题信息化如何用计算机高效地处理这些信息从创造价值 2. 什么是数据 数据是信息的…

【教程】 iOS混淆加固原理篇

摘要 本文介绍了iOS应用程序混淆加固的缘由&#xff0c;编译过程以及常见的加固类型和逆向工具。详细讨论了字符串混淆、类名、方法名混淆、程序结构混淆加密等加固类型&#xff0c;并介绍了常见的逆向工具和代码虚拟化技术。 引言 在iOS开发中&#xff0c;为了保护应用程序…

Spring Boot 项目集成camunda流程引擎

使用camunda开源工作流引擎有&#xff1a;通过docker运行、使用springboot集成、部署camunda发行包、基于源代码编译运行等多种方式。 其中&#xff0c;通过源代码编译运行的方式最为复杂&#xff0c;具体参考&#xff1a;https://lowcode.blog.csdn.net/article/details/1362…

Redis高并发分布锁实战

Redis高并发分布锁实战 问题场景 场景一: 没有捕获异常 // 仅仅加锁 // 读取 stock15 Boolean ret stringRedisTemplate.opsForValue().setIfAbsent("lock_key", "1"); // jedis.setnx(k,v) // TODO 业务代码 stock-- stringRedisTemplate.delete(&quo…

《Docker 简易速速上手小册》第6章 Docker 网络与安全(2024 最新版)

文章目录 6.1 Docker 网络概念6.1.1 重点基础知识6.1.2 重点案例&#xff1a;基于 Flask 的微服务6.1.3 拓展案例 1&#xff1a;容器间的直接通信6.1.4 拓展案例 2&#xff1a;跨主机容器通信 6.2 配置与管理网络6.2.1 重点基础知识6.2.2 重点案例&#xff1a;配置 Flask 应用的…

《Docker 简易速速上手小册》第7章 高级容器管理(2024 最新版)

文章目录 7.1 容器监控与日志7.1.1 重点基础知识7.1.2 重点案例&#xff1a;监控 Flask 应用7.1.3 拓展案例 1&#xff1a;使用 ELK Stack 收集和分析日志7.1.4 拓展案例 2&#xff1a;使用集成监控工具 7.2 性能调优与资源限制7.2.1 重点基础知识7.2.2 重点案例&#xff1a;Fl…

Linux系统中前后端分离项目部署指南

目录 一.nginx安装以及字启动 解压nginx 一键安装4个依赖 安装nginx 启动 nginx 服务 开放端口号 并且在外部访问 设置nginx自启动 二.配置负载均衡 1.配置一个tomact 修改端口号 8081端口号 2.配置负载均衡 ​编辑 三.部署前后端分离项目 1.项目部署后端 ​编辑…

基于插件实现RabbitMQ“延时队列“

1.官网下载 在添加链接描述下载rabbitmq_delayed_message_exchange 插件,本文以v3.10.0为例 1.1.上传安装包 scp /Users/hong/资料/rabbitmq_delayed_message_exchange-3.10.0.ez root10.211.55.4:/usr/local/software1.2.将文件移入RabbitMQ的安装目录下的plugins目录 m…

Docker安装MS SQL Server并使用Navicat远程连接

MS SQL Server简介 Microsoft SQL Server&#xff08;简称SQL Server&#xff09;是由微软公司开发的关系数据库管理系统&#xff0c;它是一个功能强大、性能卓越的企业级数据库平台&#xff0c;用于存储和处理大型数据集、支持高效查询和分析等操作。SQL Server 支持广泛的应…

【PostgreSQL】Windows安装PostgreSQL数据库图文详细教程

Windows安装PostgreSQL数据库图文详细教程 一、前言二、PostgreSQL简介三、软件下载四、安装步骤4.1 安装向导4.2 选择安装目录4.3 选择组件4.4 选择数据存放目录4.5 选择密码4.6 选择端口号4.7 等待安装完成4.8 取消勾选&#xff0c;安装完成 五、启动教程5.1 搜索pgAdmin4&am…

常见的音频与视频格式

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…