在上一章节,我们深入分析了 Linux 进程与线程的创建过程,探讨了 fork()
和 clone()
的实现细节。然而,进程的生命周期不仅仅止步于创建,在上节中我们说到,在创建完task_struct结构体之后,将新的任务放在调度队列上等待调度。而调度是进程从“创建”走向“运行”的关键环节。
Linux 内核调度器负责在多个任务之间合理分配 CPU 资源,以确保系统的高效性、公平性和实时性。随着计算机硬件的发展,Linux 进程调度器经历了从 O(n) 复杂度的调度算法,到 O(1) 调度器,再到 CFS 的演进。最近,Linux 内核又引入了 EEVDF(Earliest Eligible Virtual Deadline First)作为对 CFS 的改进,以进一步优化低延迟任务的公平性和响应性。
本篇文章将围绕 Linux 进程调度器的演进、CFS 调度器的核心机制,以及 EEVDF 调度算法 展开详细分析,探索调度器如何影响进程运行的效率与公平性。
调度器的发展历史
1.O(n)调度器的发展过程
之前到2.4版本的Linux调度器经历了先来先服务、短作业优先、时间片轮转以及优先级时间片轮转的发展过程,一步一步弥补了之前的不足:
调度算法 | 主要思想 | 改进点(相较于前一算法) | 主要不足 |
---|---|---|---|
先来先服务 | 按照进程到达的顺序进行调度,先到先执行,直到进程完成 | 最基础的调度算法,实现简单 | 可能导致“短作业等待长作业”问题,容易产生“饥饿”现象 |
短作业优先 | 选择最短的作业优先运行,减少平均等待时间 | 解决先来先服务 的“短作业等待长作业”问题,提高吞吐量 | 需要提前知道 CPU Burst Time,不适用于动态环境,可能导致长作业“饥饿” |
时间片轮转 | 每个进程被分配一个固定时间片,时间片到后被切换 | 解决短作业优先可能导致的“饥饿”问题,所有进程公平获得 CPU 时间 | 可能增加上下文切换的开销,选择合适的时间片长度较难,且有的进程较为“迫切“ |
优先级时间片轮转 | 在时间片轮转的基础上引入优先级,高优先级的进程比低优先级的进程更容易获得 CPU | 结合了优先级调度和 RR,保证高优先级进程快速响应,同时维持公平性 | 低优先级进程可能长期得不到调度,可能需要引入“优先级提升”机制 |
动态优先级时间片轮转 | 在 静态优先级 的基础上,引入 动态优先级 机制,根据进程行为、等待时间等动态调整优先级 | 解决固定优先级调度的“饥饿”问题,使得长期等待的低优先级进程能逐渐获得 CPU 机会 |
2.Linux2.5 O(1)调度器
上述算法的复杂度都是O(n),但是随着多核系统的出现,若多个CPU都使用同一个运行队列,则会导致锁竞争加剧。且遍历整个运行队列也较为低效,故发展重点转为了如何提高多核系统下的调度器的性能。
在Linux2.5中,针对上述问题,每个CPU都维护一个运行队列,减少了锁竞争。并且引入了多优先级任务队列:即每一个优先级有一个队列,数值越低,优先级越高。并且在查找时引入了bitmap辅助实现O(1)查找,通过每个比特来表示对应的优先级是否有任务。逻辑图大致如下:
并且每一个runqueue上有两个任务队列:active(当前周期要调度的进程列表)、expired(当前周期时间片用完的进程列表)。当active中的task全部执行完毕时,active和expired队列会交换,进行下一个周期的调度运转:
然而在调度优先级上,实时进程和普通进程是不同的,在实时进程中,优先级是静态的,高优先级的进程一定会先调度,使用SCHED_FIFO和SCHED_RR去分配CPU。
但对于普通进程来说,使用的是上面讲过的动态优先级的方法,静态优先级不变,但是动态优先级会随着对时间片的使用来动态的调整:
优先级 | 类型 | 适用调度策略 | 适用场景 |
---|---|---|---|
0-99 | 实时优先级 | SCHED_FIFO(先来先服务) SCHED_RR(时间片轮转) | 硬件中断处理、音视频处理、工业控制 |
100-139 | 普通优先级 | SCHED_NORMAL(CFS) SCHED_BATCH(批处理) SCHED_IDLE | 绝大部分用户进程,如浏览器、编译任务等 |
调度策略
进程线程的调度依赖于调度策略,可以使用chrt命令查看当前系统支持的调度策略:
SCHED_OTHER 最小/最大优先级 : 0/0
SCHED_FIFO 最小/最大优先级 : 1/99
SCHED_RR 最小/最大优先级 : 1/99
SCHED_BATCH 最小/最大优先级 : 0/0
SCHED_IDLE 最小/最大优先级 : 0/0
SCHED_DEADLINE 最小/最大优先级 : 0/0
其各个特点如下表:
调度策略 | 优先级范围 | 调度特点 | 适用场景 |
---|---|---|---|
SCHED_OTHER (默认) | 100-139 | 默认调度策略(CFS) - 使用 nice 值(-20~19)调整权重 - 采用 红黑树 实现公平调度 | 普通用户进程(大多数应用程序) |
SCHED_FIFO(实时) | 1-99 | 固定优先级调度 - 只要进程不主动放弃 CPU,它会一直运行 | 实时任务(音视频处理、工业控制) |
SCHED_RR(实时) | 1-99 | 类似 FIFO,但使用时间片轮转 - 相同优先级的进程轮流执行,防止饥饿 | 需要公平 CPU 时间的实时任务(如网络包处理) |
SCHED_BATCH | 100-139 | 适用于 CPU 密集型任务 - 进程不会主动抢占 CPU,只会在空闲时运行 | 批处理任务(大规模数据处理、机器学习训练) |
SCHED_IDLE | 100-139 | 仅在 CPU 空闲时运行 - 适用于 低优先级任务 | 后台计算、不重要的任务(如文件索引、日志分析) |
SCHED_DEADLINE | 最高优先级的实时进程 | 基于 Earliest Deadline First调度算法 - 适用于 硬实时任务 - 需要设置 运行时间、周期、截止时间 | 严格实时任务(机器人控制、金融交易) |
CFS调度器
在 Linux 进程调度中,CFS(Completely Fair Scheduler)完全公平调度器 负责普通进程的调度。CFS 通过调度实体维护进程的调度信息,核心指标是 虚拟运行时间 vruntime
,它决定了进程在调度队列中的优先级。
1.sched_entity
在 task_struct
结构体中,每个普通进程都有一个 sched_entity
,它包含进程的调度权重(weight) 和 虚拟运行时间(vruntime) 等信息:
struct sched_entity {
struct load_weight load; // 进程的调度权重
u64 vruntime; // 进程的虚拟运行时间
struct list_head group_node;
struct cfs_rq *cfs_rq; // 所属的 CFS 运行队列
...
};
2.计算进程权重(weight)
CFS 使用 nice
值来计算进程的调度权重,nice
取值范围为 -20
(最高优先级)到 19
(最低优先级)。
nice=0
的进程默认权重为 1024
,其余 nice
值的权重可通过查表获取:
const int sched_prio_to_weight[40] = {
/* -20 */ 88761, 71755, 56483, 46273, 36291,
/* -15 */ 29154, 23254, 18705, 14949, 11916,
/* -10 */ 9548, 7620, 6100, 490 4, 3906,
/* -5 */ 3121, 2501, 1991, 1586, 1277,
/* 0 */ 1024, 820, 655, 526, 423,
/* 5 */ 335, 272, 215, 172, 137,
/* 10 */ 110, 87, 70, 56, 45,
/* 15 */ 36, 29, 23, 18, 15,
};
内核中提供一个函数set_load_weight
来查表,用来计算一个任务的权重,即上述调度实体结构体中的load。
void set_load_weight(struct task_struct *p, bool update_load){//p是设置调度权重的进程,
int prio = p->static_prio - MAX_RT_PRIO;//得到用于 sched_prio_to_weight[] 的索引
struct load_weight lw;
if (task_has_idle_policy(p)) {
lw.weight = scale_load(WEIGHT_IDLEPRIO);
lw.inv_weight = WMULT_IDLEPRIO;
} else {
lw.weight = scale_load(sched_prio_to_weight[prio]);
lw.inv_weight = sched_prio_to_wmult[prio];
}//计算调度权重,判断任务类型
if (update_load && p->sched_class->reweight_task)
p->sched_class->reweight_task(task_rq(p), p, &lw);
else
p->se.load = lw;//更新进程的负载
......
}
3.vruntime的计算
在 CFS 调度器中,vruntime
作为调度依据,vruntime
最小的进程会被优先调度。
vruntime
的计算公式:
vruntime = nice_0_weight ⋅ delta_exec weight \text{vruntime} = \frac{\text{nice\_0\_weight} \cdot \text{delta\_exec}}{\text{weight}} vruntime=weightnice_0_weight⋅delta_exec
在内核中的实现源码为:
static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
{
if (unlikely(se->load.weight != NICE_0_LOAD))
delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
//计算进程 vruntime 增长的调整值 delta
return delta;
}
当 weight ≠ 1024
时,调用 __calc_delta()
进行缩放计算,使得 nice=-20
的进程 vruntime
增长最慢,nice=19
的进程 vruntime
增长最快。
static u64 __calc_delta(u64 delta_exec, unsigned long weight, struct load_weight *lw) {
u64 fact = scale_load_down(weight);
u32 fact_hi = (u32)(fact >> 32);
int shift = WMULT_SHIFT;
int fs;
__update_inv_weight(lw); // 更新反权重 inv_weight
if (unlikely(fact_hi)) {
fs = fls(fact_hi);
shift -= fs;
fact >>= fs;
}
fact = mul_u32_u32(fact, lw->inv_weight);
return mul_u64_u32_shr(delta_exec, fact, shift);
}
4.cfs_rq
该结构体表示CFS就绪队列的数据结构,其定义如下:
struct cfs_rq {
struct load_weight load; /* 运行队列的总权重 */
unsigned int nr_running; /* 队列中的进程数 */
unsigned int h_nr_running; /* 仅统计 `SCHED_NORMAL/BATCH/IDLE` 任务 */
unsigned int idle_nr_running; /* `SCHED_IDLE` 任务数量 */
unsigned int idle_h_nr_running; /* `SCHED_IDLE` 任务的高优先级任务数 */
unsigned int h_nr_delayed; /* 被延迟执行的任务数量 */
s64 avg_vruntime; /* 任务的平均 `vruntime` */
u64 avg_load; /* 任务的平均负载 */
u64 min_vruntime; /* 运行队列中的最小 `vruntime` */
struct rb_root_cached tasks_timeline; /* CFS 任务的红黑树 */
struct sched_entity *curr; /* 当前运行的进程 */
struct sched_entity *next; /* 下一个要运行的进程 */
......
};
在这个对象中,核心为rb_root_cached类型的tasks_timeline对象,这就是CFS调度器管理就绪任务的红黑树,红黑树中存放的是sched_entity调度实体,而所有可运行的调度实体按照vruntime排序插入红黑树。整体结构如下图所示。
CFS选择红黑树最左边的进程运行。随着系统时间的推移,原来左边运行过的进程慢慢的会移动到红黑树的右边,原来右边的进程也会最终跑到最左边。因此红黑树中的每个进程都有机会运行。
EEVDF调度器
与 CFS 类似,EEVDF 的目标是在相同优先级的所有可运行任务之间均等地分配 CPU 时间。为此,它为每个任务分配了一个虚拟运行时间,并计算出一个“滞后”值,用以判断某任务是否获得了其应有的 CPU 时间份额。具体来说,正滞后值表示该任务欠 CPU 时间,而负滞后值则表示任务已超出其分配。EEVDF 会选择滞后值大于或等于零的任务,并为每个任务计算一个虚拟截止时间(Vitual Deadline),然后选择虚拟截止时间最早的任务作为下一执行任务。这一机制允许时间片较短的延迟敏感任务获得优先,从而提高它们的响应性。
目前对于如何管理滞后值仍存在讨论,尤其是针对处于睡眠状态的任务;但截至目前,EEVDF 使用了一种基于虚拟运行时间(VRT)的“衰减”机制。这种机制防止任务通过短暂睡眠来重置其负滞后值:当任务进入睡眠状态时,它仍保留在就绪队列中,但被标记为“延迟出队”,使得其滞后值随着虚拟运行时间而衰减。因此,长时间睡眠的任务最终会重置其滞后值。最后,当某个任务的虚拟截止时间更早时,可以抢占其他任务,同时任务还可以使用新的 sched_setattr() 系统调用请求特定的时间片,这进一步便利了延迟敏感应用程序的调度。
简单来说,EEVDF 调度器的原理可以比作一个“欠账系统”。每个任务都有一个“账单”,账单上记录了它应得的 CPU 时间和实际得到的 CPU 时间之间的差额,这个差额叫做“lag”。具体原理如下:
-
公平分账: 每个任务根据它的权重应得一部分 CPU 时间。如果一个任务实际获得的时间少于它应得的部分,它就“欠”了一些时间(lag 为正);反之则是“超前”了(lag 为负)。
2.资格判断:当系统的虚拟时间达到任务的某个“门槛时间”时,这个任务就有资格运行。如果任务欠账,它会立即进入候选队列;如果超前,则需要等到其他任务赶上。 -
设定截止日期:每次任务请求一段 CPU 时间时,根据它的资格时间和请求长度,调度器会计算一个虚拟截止时间。可以把截止时间看作任务“还账”的最后期限——在这个时刻前任务应完成获得所请求的时间。
4.调度决策:调度器总是选择截止时间最早的任务执行。这样,那些欠账多的任务(截止时间早)会优先获得 CPU 时间,以尽快补上它们的欠款,从而实现整体公平。
总之,EEVDF 就是通过跟踪任务应得和实际获得的 CPU 时间差(lag),并根据任务的权重和请求长度为每个任务设定一个虚拟截止时间,最终始终优先选择截止时间最早的任务运行,从而确保所有任务最终都能按公平比例获得 CPU 时间。
其主要流程如下:
关键概念
1.公平运行时间
S ( t 1 , t 2 ) = ∫ t 1 t 2 w i W d t S(t_1, t_2) = \int_{t_1}^{t_2} \frac{w_i}{W} \, dt S(t1,t2)=∫t1t2Wwidt
该公式表示任务 i 在时间段 [t1,t2] 内应获得的 CPU 时间。wi 是任务 i 的权重。W 是系统中所有活跃任务的权重总和。
直观上说,如果任务 i 的权重占所有任务权重的比例为W/wi,那么在任意时刻,它就“理应”分得这部分比例的 CPU 时间。通过对时间段内这一比例积分,就得到了任务 i 在这段时间内应该获得的运行时间 S(t_1, t_2) 。
2.系统虚拟时间
V t = ∫ 0 t 1 W d t V_t = \int_{0}^{t} \frac{1}{W} \, dt Vt=∫0tW1dt
其作用是将实际时间与系统中任务的总权重联系起来,提供一种度量方式,使得调度器能根据系统负载动态地计算各任务应得的时间。
利用虚拟时间 Vt 的定义,可以将公平运行时间公式重写为:
S
(
t
1
,
t
2
)
=
w
i
(
V
t
2
−
V
t
1
)
S(t_1, t_2) = w_i (V_{t_2} - V_{t_1})
S(t1,t2)=wi(Vt2−Vt1)
这表示任务 i在时间段 [t1,t2] 内应获得的运行时间,等于它的权重 wi 与虚拟时间从 t1到 t2的增长量的乘积。
3.eligible time合格时间
任务 i 的 eligible time 是指这样一个时刻:从任务开始活跃时刻 t0到这个时刻,任务 i 实际获得的运行时间 s(t0,t) 正好等于它应得的运行时间 S(t0,e)(“应得时间”是根据任务权重计算出来的公平分配时间)。
换句话说,eligible time 就是任务 i 完全“赶上”它应得 CPU 时间的那一点,在这个时刻任务 i 的运行时间与应得运行时间相等,此时虚拟时间记为 Ve,即:
S
(
t
0
,
e
)
=
s
(
t
0
,
t
)
S(t_0, e) = s(t_0, t)
S(t0,e)=s(t0,t)
结合
S
(
t
0
,
e
)
=
w
i
(
V
e
−
V
t
0
)
S(t_0, e) = w_i(V_e - V_{t_0})
S(t0,e)=wi(Ve−Vt0)
可以得到:
w
i
(
V
e
−
V
t
0
)
=
s
(
t
0
,
t
)
w_i(V_e - V_{t_0}) = s(t_0, t)
wi(Ve−Vt0)=s(t0,t)
从这里解出Ve得到:
V
e
=
V
t
0
+
s
(
t
0
,
t
)
w
i
V_e = V_{t_0} + \frac{s(t_0, t)}{w_i}
Ve=Vt0+wis(t0,t)
因此,eligible time Ve 就表示任务 i 在虚拟时间上“赶上”它应得的 CPU 时间所需要达到的虚拟时间点。当系统的当前虚拟时间 Vt大于或等于 Ve时,就认为任务 i 的请求变得“合格”,也就是说它有资格作为候选任务去占用 CPU。
4.deadline time截止时间
deadline time 是指任务 i 的请求在虚拟时间区间[Ve,Vd] 内可以获得的运行时间刚好等于请求长度 r。也就是说,从虚拟时间 Ve 开始,到 Vd 为止,按照任务的权重分配,任务 i 应该获得的运行时间等于 r。
换句话说,deadline time 就像是任务的“截止日期”——到了这个虚拟时刻,任务的这次请求理论上应该已经得到了满足。
根据上述定义为:
S
(
e
,
d
)
=
r
S(e, d) = r
S(e,d)=r
改写为:
w
i
(
V
d
−
V
e
)
=
r
w_i(V_d - V_e) = r
wi(Vd−Ve)=r
解出Vd:
V
d
=
V
e
+
r
w
i
V_d = V_e + \frac{r}{w_i}
Vd=Ve+wir
5.多次请求下的递推公式
对于任务 i 的连续多个请求(假设每次请求的长度都是 r),我们有如下递推关系:
- 第一次请求:
V e 1 = V t 0 和 V d 1 = V t 0 + r w i V_{e1} = V_{t_0} \quad \text{和} \quad V_{d1} = V_{t_0} + \frac{r}{w_i} Ve1=Vt0和Vd1=Vt0+wir
- 后续第 k 次请求:
V e k = V d k − 1 和 V d k = V e k + r w i V_{e_k} = V_{d_{k-1}} \quad \text{和} \quad V_{d_k} = V_{e_k} + \frac{r}{w_i} Vek=Vdk−1和Vdk=Vek+wir
这意味着每次新的请求的起点(eligible time)都等于上一次请求的截止时间,而新的截止时间则在前一次基础上增加了 r/wi的虚拟时间。
举例说明
我们可以利用前面介绍的公式来看看 c1 和 c2两个任务在这种情况下如何计算它们的 eligible time 和 deadline time。
假设两个任务的权重相同,都为 2,即
w
1
=
w
2
=
2
w_1 = w_2 = 2
w1=w2=2
对于 c1
-
c1 在 t=0 加入,此时我们假定它开始活跃的系统虚拟时间为 Vt0=0。
-
c1 的请求长度 r1=2。
-
**Eligible time 的计算:**由于 c1 刚加入,还没有运行,所以它在发起请求时的实际运行时间为 0,故
V e 1 = V t 0 + s ( t 0 , t ) w 1 = 0 + 0 2 = 0. V_{e1} = V_{t0} + \frac{s(t_0, t)}{w_1} = 0 + \frac{0}{2} = 0. Ve1=Vt0+w1s(t0,t)=0+20=0.
这表示 c1 从一开始就“欠”它应得的时间。
- Deadline time 的计算:
根据公式
V
d
=
V
e
+
r
w
i
V_d = V_e + \frac{r}{w_i}
Vd=Ve+wir
对 c1 有
V
d
1
=
V
e
1
+
r
1
w
1
=
0
+
2
2
=
1
V_{d1} = V_{e1} + \frac{r_1}{w_1} = 0 + \frac{2}{2} = 1
Vd1=Ve1+w1r1=0+22=1
对于c2
- c2 在 t=1加入。此时,系统的虚拟时间 Vt0 对于 c2 是它加入时刻对应的虚拟时间。
假设系统在这段期间内一直由 c1 独占运行(实际情况会依赖于调度器如何推进虚拟时间,但为了示例我们假定系统虚拟时间与实际时间接近,即 V(1)=1),那么 c2 的起始虚拟时间为 1。
- 请求长度:c2 的请求长度 r2=1。
- **Eligible time 的计算:**c2 刚加入时还未获得任何运行时间,所以
V e 2 = V ( 1 ) + 0 w 2 = 1 V_{e2} = V(1) + \frac{0}{w_2} = 1 Ve2=V(1)+w20=1
- Deadline time 的计算:
V d 2 = V e 2 + r 2 w 2 = 1 + 1 2 = 1.5 V_{d2} = V_{e2} + \frac{r_2}{w_2} = 1 + \frac{1}{2} = 1.5 Vd2=Ve2+w2r2=1+21=1.5
因此,在调度决策中,调度器会优先考虑那些拥有较早截止时间的任务。在这个例子中,c1 的截止时间 1 早于 c2 的 1.5,所以即使 c2在 t=1加入竞争,c1 的请求因其更早的截止时间将被优先满足。
这就是如何利用权重、请求长度以及系统虚拟时间来计算并比较两个任务的 eligible time 和 deadline time,从而决定哪个任务更早满足其公平分配的要求。
lag的概念
任务 i 应得时间和已得时间不总是相等的,两者的差值称作 lag:
lag
i
(
t
)
=
S
i
(
t
0
,
t
)
−
s
i
(
t
0
,
t
)
\text{lag}_i(t) = S_i(t_0, t) - s_i(t_0, t)
lagi(t)=Si(t0,t)−si(t0,t)
这个 lag 值反映了任务是否“欠”运行时间(lag 为正)或者“超前”运行时间(lag 为负)。
任务退出竞争时的情况
假设任务 a 在某时刻 t 退出了竞争。为了确保系统的公平性,其它仍然活跃的任务需要“接管”任务 a 剩余的未补偿的运行时间(也就是任务 a 的 lag)。这就是“lag”重新分配的思想。
1.退出前后系统虚拟时间的更新
当任务 a 退出时,系统中其他任务在 [t0,t+] 这段时间内可分配的运行时间,会因为任务 a 的退出而发生变化。
S
i
(
t
0
,
t
+
)
=
(
t
−
t
0
−
s
a
(
t
0
,
t
)
)
w
i
W
S_i(t_0, t_+) = (t - t_0 - s_a(t_0, t)) \frac{w_i}{W}
Si(t0,t+)=(t−t0−sa(t0,t))Wwi
- t−t0 是从任务开始活跃到当前的总时长;
- sa(t0,t) 是任务 a 在这段时间内实际获得的运行时间;
- W 是任务 a 退出后,系统中剩余所有任务的权重之和。
2.利用任务 a 的 lag 表达 sa(t0,t)
根据 lag 的定义,对于任务 a 有:
lag
a
(
t
)
=
S
a
(
t
0
,
t
)
−
s
a
(
t
0
,
t
)
\text{lag}_a(t) = S_a(t_0, t) - s_a(t_0, t)
laga(t)=Sa(t0,t)−sa(t0,t)
可推算:
s
a
(
t
0
,
t
)
=
(
t
−
t
0
)
w
a
W
−
lag
a
(
t
)
s_a(t_0, t) = (t - t_0) \frac{w_a}{W} - \text{lag}_a(t)
sa(t0,t)=(t−t0)Wwa−laga(t)
3.推导系统虚拟时间的更新
S i ( t 0 , t + ) = w i ( V t − V t 0 ) + w i W lag a ( t ) S_i(t_0, t_+) = w_i(V_t - V_{t_0}) + \frac{w_i}{W} \text{lag}_a(t) Si(t0,t+)=wi(Vt−Vt0)+Wwilaga(t)
这说明,在任务 a 退出后,其他任务 i 的应得运行时间在系统虚拟时间中的增量不仅包括原来的部分 wi(Vt−Vt0),还额外获得了“补偿”。
由此,系统虚拟时间也相应地更新为:
V
t
+
=
V
t
+
lag
a
(
t
)
W
V_{t_+} = V_t + \frac{\text{lag}_a(t)}{W}
Vt+=Vt+Wlaga(t)
4.其它任务的 lag 更新
对于任意其他任务 i,新的 lag 就变为原来的 lag 加上因任务 a 退出而“转嫁”来的那部分:
lag
i
(
t
+
)
=
lag
i
(
t
)
+
w
i
W
lag
a
(
t
)
\text{lag}_i(t_+) = \text{lag}_i(t) + \frac{w_i}{W} \text{lag}_a(t)
lagi(t+)=lagi(t)+Wwilaga(t)
这就意味着,退出任务 a 的 lag 会根据各个任务的权重比例均匀地加到剩余任务的 lag 上。
总结
在本篇文章中,我们回顾了 Linux 进程调度器的演进,从早期的 O(n) 复杂度调度算法,到 O(1) 调度器,再到目前主流的 CFS,并分析了 EEVDF 在 CFS 之上的改进。
然而,理解调度器的原理仅仅是第一步,在下一篇文章中,我们将深入 Linux 内核源码,分析具体的调度流程。