从几个问题开始理解CFS调度器

本文深入探讨了Linux CFS(完全公平调度器)的工作原理,包括新进程与休眠进程的vruntime初始化策略、唤醒抢占特性及如何避免不公等问题。

CFS(完全公平调度器)是Linux内核2.6.23版本开始采用的进程调度器,它的基本原理是这样的:设定一个调度周期(sched_latency_ns),目标是让每个进程在这个周期内至少有机会运行一次,换一种说法就是每个进程等待CPU的时间最长不超过这个调度周期;然后根据进程的数量,大家平分这个调度周期内的CPU使用权,由于进程的优先级即nice值不同,分割调度周期的时候要加权;每个进程的累计运行时间保存在自己的vruntime字段里,哪个进程的vruntime最小就获得本轮运行的权利。

那么问题就来了:

新进程的vruntime的初值是不是0啊?

假如新进程的vruntime初值为0的话,比老进程的值小很多,那么它在相当长的时间内都会保持抢占CPU的优势,老进程就要饿死了,这显然是不公平的。所以CFS是这样做的:每个CPU的运行队列cfs_rq都维护一个min_vruntime字段,记录该运行队列中所有进程的vruntime最小值,新进程的初始vruntime值就以它所在运行队列的min_vruntime为基础来设置,与老进程保持在合理的差距范围内。参见后面的源代码。

新进程的vruntime初值的设置与两个参数有关:
sched_child_runs_first:规定fork之后让子进程先于父进程运行;
sched_features的START_DEBIT位:规定新进程的第一次运行要有延迟。

注:
sched_features是控制调度器特性的开关,每个bit表示调度器的一个特性。在sched_features.h文件中记录了全部的特性。START_DEBIT是其中之一,如果打开这个特性,表示给新进程的vruntime初始值要设置得比默认值更大一些,这样会推迟它的运行时间,以防进程通过不停的fork来获得cpu时间片。

如果参数 sched_child_runs_first打开,意味着创建子进程后,保证子进程会在父进程之前运行。

子进程在创建时,vruntime初值首先被设置为min_vruntime;然后,如果sched_features中设置了START_DEBIT位,vruntime会在min_vruntime的基础上再增大一些。设置完子进程的vruntime之后,检查sched_child_runs_first参数,如果为1的话,就比较父进程和子进程的vruntime,若是父进程的vruntime更小,就对换父、子进程的vruntime,这样就保证了子进程会在父进程之前运行。

休眠进程的vruntime一直保持不变吗?

如果休眠进程的 vruntime 保持不变,而其他运行进程的 vruntime 一直在推进,那么等到休眠进程终于唤醒的时候,它的vruntime比别人小很多,会使它获得长时间抢占CPU的优势,其他进程就要饿死了。这显然是另一种形式的不公平。CFS是这样做的:在休眠进程被唤醒时重新设置vruntime值,以min_vruntime值为基础,给予一定的补偿,但不能补偿太多。

 休眠进程在唤醒时会立刻抢占CPU吗?

这是由CFS的唤醒抢占 特性决定的,即sched_features的WAKEUP_PREEMPT位。

由于休眠进程在唤醒时会获得vruntime的补偿,所以它在醒来的时候有能力抢占CPU是大概率事件,这也是CFS调度算法的本意,即保证交互式进程的响应速度,因为交互式进程等待用户输入会频繁休眠。除了交互式进程以外,主动休眠的进程同样也会在唤醒时获得补偿,例如通过调用sleep()、nanosleep()的方式,定时醒来完成特定任务,这类进程往往并不要求快速响应,但是CFS不会把它们与交互式进程区分开来,它们同样也会在每次唤醒时获得vruntime补偿,这有可能会导致其它更重要的应用进程被抢占,有损整体性能。我曾经处理过的一个案例:服务器上有两类应用进程,A进程定时循环检查有没有新任务,如果有的话就简单预处理后通知B进程,然后调用nanosleep()主动休眠,醒来后再重复下一个循环;B进程负责数据运算,是CPU消耗型的;B进程的运行时间很长,而A进程每次运行时间都很短,但睡眠/唤醒却十分频繁,每次唤醒就会抢占B,导致B的运行频繁被打断,大量的进程切换带来很大的开销,整体性能下降很厉害。那有什么办法吗?有,CFS可以禁止唤醒抢占 特性:

禁用唤醒抢占 特性之后,刚唤醒的进程不会立即抢占运行中的进程,而是要等到运行进程用完时间片之后。在以上案例中,经过这样的调整之后B进程被抢占的频率大大降低了,整体性能得到了改善。

如果禁止唤醒抢占特性对你的系统来说太过激进的话,你还可以选择调大以下参数:

sched_wakeup_granularity_ns
这个参数限定了一个唤醒进程要抢占当前进程之前必须满足的条件:只有当该唤醒进程的vruntime比当前进程的vruntime小、并且两者差距(vdiff)大于sched_wakeup_granularity_ns的情况下,才可以抢占,否则不可以。这个参数越大,发生唤醒抢占就越不容易。

进程占用的CPU时间片可以无穷小吗?

假设有两个进程,它们的vruntime初值都是一样的,第一个进程只要一运行,它的vruntime马上就比第二个进程更大了,那么它的CPU会立即被第二个进程抢占吗?答案是这样的:为了避免过于短暂的进程切换造成太大的消耗,CFS设定了进程占用CPU的最小时间值,sched_min_granularity_ns,正在CPU上运行的进程如果不足这个时间是不可以被调离CPU的。

sched_min_granularity_ns发挥作用的另一个场景是,本文开门见山就讲过,CFS把调度周期sched_latency按照进程的数量平分,给每个进程平均分配CPU时间片(当然要按照nice值加权,为简化起见不再强调),但是如果进程数量太多的话,就会造成CPU时间片太小,如果小于sched_min_granularity_ns的话就以sched_min_granularity_ns为准;而调度周期也随之不再遵守sched_latency_ns,而是以 (sched_min_granularity_ns * 进程数量) 的乘积为准。

进程从一个CPU迁移到另一个CPU上的时候vruntime会不会变?

在多CPU的系统上,不同的CPU的负载不一样,有的CPU更忙一些,而每个CPU都有自己的运行队列,每个队列中的进程的vruntime也走得有快有慢,比如我们对比每个运行队列的min_vruntime值,都会有不同:

如果一个进程从min_vruntime更小的CPU (A) 上迁移到min_vruntime更大的CPU (B) 上,可能就会占便宜了,因为CPU (B) 的运行队列中进程的vruntime普遍比较大,迁移过来的进程就会获得更多的CPU时间片。这显然不太公平。

CFS是这样做的:
当进程从一个CPU的运行队列中出来 (dequeue_entity) 的时候,它的vruntime要减去队列的min_vruntime值;
而当进程加入另一个CPU的运行队列 ( enqueue_entiry) 时,它的vruntime要加上该队列的min_vruntime值。
这样,进程从一个CPU迁移到另一个CPU之后,vruntime保持相对公平。

<think>好的,我现在需要帮助用户比较CFS调度器与O(n)和O(1)调度器的区别,并了解它们的算法核心。首先,我得回忆一下之前用户的问题和我之前的回答。用户之前询问了O(n)和O(1)调度器的联系及核心算法,现在想扩展到CFS调度器。 首先,我需要明确这三个调度器的基本概念和时间线。O(n)调度器是早期的,采用单队列轮转,时间复杂度高;O(1)调度器通过双优先级数组和位图优化到O(1)复杂度;而CFS则是后续引入的,使用红黑树和虚拟时间来实现公平调度。用户现在希望了解这三者之间的差异,尤其是CFS与其他两者的不同。 接下来,我需要参考用户提供的引用内容。引用[1]提到CFS与O(1)的比较,以及它们的设计差异。引用[2]指出O(1)调度器进程增多时的性能问题,以及优先级设置的复杂性。这些信息有助于构建对比的框架。 用户可能希望得到结构化的回答,包括历史背景、核心差异、算法实现以及优缺点比较。我需要确保覆盖这些方面,同时保持技术细节的准确性。例如,CFSvruntime概念和红黑树结构是关键点,需要详细解释。 外,用户可能对时间复杂度特别关注,之前的问题中已经提到了O(n)和O(1)的时间复杂度,现在需要将CFS的O(log n)复杂度融入比较中。同时,要强调CFS在公平性和交互性方面的改进,比如避免O(1)调度器中复杂的优先级计算问题。 我需要检查之前的回答是否已经涉及了部分内容,避免重复,同时补充新的信息。例如,之前提到O(1)调度器CFS取代的原因,现在需要详细说明CFS如何改进这些不足。 可能还需要考虑用户的实际应用场景,比如为什么CFS更适合现代多任务环境,或者在高负载下的表现如何。这些实际例子能帮助用户更好地理解差异。 最后,确保引用标记正确,参考用户提供的引用内容,但也要结合自己的知识库来补充详细信息。需要验证每个技术点是否正确,比如红黑树的操作复杂度是否确实是O(log n),以及vruntime的计算方法。 总结一下,我需要组织内容为以下几个部分: 1. 历史演进背景 2. 核心设计差异(数据结构、时间复杂度、公平性机制) 3. 算法实现细节对比 4. 优缺点及适用场景 5. 总结三者的发展脉络 同时,生成相关问题时要覆盖用户可能进一步想知道的内容,比如CFS的具体实现细节,或者在不同负载下的性能表现等。</think>### CFS调度器与O(n)/O(1)调度器对比解析 --- #### 一、历史演进脉络 **技术演进链**: `O(n)调度器(Linux 2.4)` → `O(1)调度器(Linux 2.6)` → `CFS调度器(Linux 2.6.23+)` **核心矛盾驱动**: 从时间片轮转向公平调度演进,解决**交互式任务延迟**与**高并发效率**问题[^1][^2] --- #### 二、核心差异对比表 | 维度 | O(n)调度器 | O(1)调度器 | CFS调度器 | |---------------------|----------------------------|----------------------------|----------------------------| | **时间复杂度** | O(n) | O(1) | O(log n) | | **数据结构** | 单链表轮转 | 双优先级数组+位图 | 红黑树(按vruntime排序) | | **公平性机制** | 简单轮转导致饥饿现象 | 动态优先级补偿(复杂启发) | 虚拟时间(vruntime)精确计算 | | **交互响应** | 高延迟(秒级)[^3] | 通过优先级奖励优化 | 自然保障低延迟 | | **多核扩展性** | 全局锁瓶颈 | 每CPU队列 | 每CPU红黑树+负载均衡 | | **典型缺陷** | 进程数↑→性能断崖下降 | 启发式算法导致代码臃肿 | 内存占用较高 | --- #### 三、算法核心实现解析 ##### 1. **O(n)调度器** - **核心逻辑**:遍历所有就绪进程链表选择最高优先级任务 ```c struct task_struct *pick_next_task() { for_each_process(p) { // O(n)遍历 if (p->prio < best_prio) best = p; } return best; } ``` - **缺陷**:当进程数$n \geq 10^3$时,调度延迟显著增加[^3] ##### 2. **O(1)调度器** - **核心结构**: - **两个优先级数组**:`active`(当前可运行)与`expired`(时间片耗尽) - **位图加速**:通过`find_first_bit()`快速定位最高优先级队列(固定140级) - **动态时间片**: $$ \text{time\_slice} = \text{base\_time} \times (140 - \text{prio}) / 140 $$ 优先级高的进程获得更长时间片[^2] ##### 3. **CFS调度器** - **核心概念**: - **虚拟时间(vruntime)**: $$ vruntime = \text{实际运行时间} \times \frac{NICE\_0\_LOAD}{\text{进程权重}} $$ 权重由`nice`值决定(权重比按对数比例变化)[^1] - **红黑树排序**:所有可运行进程按`vruntime`排序,左节点为最小vruntime - **调度流程**: 1. 选择红黑树最左节点(最小vruntime进程) 2. 计算其可运行时间: $$ \text{timeslice} = \frac{\text{sched\_latency} \times \text{weight}}{\sum \text{weights}} $$ 3. 进程运行时持续更新vruntime 4. 若进程用完时间片,则重新插入红黑树 --- #### 四、关键差异场景对比 **场景:100个CPU密集型 + 10个交互式进程并发** - **O(n)**:交互进程需等待遍历完100个进程,响应延迟高达数十ms - **O(1)**:通过优先级奖励机制提升交互进程优先级,但需复杂计算 - **CFS**:交互进程因实际运行时间少,vruntime增长慢,自然优先被调度 --- #### 五、演进核心突破 1. **CFS对O(1)的改进** - 消除**优先级反转问题**(通过vruntime的数学公平性) - 避免O(1)的**经验公式缺陷**(如交互进程检测启发式算法) - 实现**完全公平调度**而非优先级驱动 2. **时间复杂度优化路径** $$ O(n) \xrightarrow{\text{位图}} O(1) \xrightarrow{\text{红黑树}} O(\log n) $$ 虽时间复杂度增加,但实际效率更高(因硬件缓存优化) --- #### 六、总结与启示 | 调度器 | 核心优势 | 主要缺陷 | 适用场景 | |---------|---------------------------|-------------------------|----------------------| | O(n) | 实现简单 | 无法扩展的高延迟 | 低并发嵌入式系统 | | O(1) | 高吞吐量 | 启发式算法不可预测 | 服务器批处理任务 | | CFS | 数学公平性保障 | 内存占用较高 | 桌面/实时混合负载 | --- ### 相关问题 1. CFS调度器中`vruntime`的权重计算如何影响进程优先级? 2. 红黑树在CFS调度中如何实现高效的最小节点查找? 3. O(1)调度器的优先级位图(bitmap)具体如何工作? 4. CFS调度器如何处理实时进程与普通进程的混合调度? [^1]: CFS虚拟时间与公平调度原理 [^2]: O(1)调度器动态优先级机制 [^3]: 早期调度器高延迟问题分析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值