接上篇:【Linux内核二十四】进程管理模块:CFS调度器dequeue_task_fair方法(二)
入队的enqueue和出队的dequeue都说完了,现在继续下一个。
来说说cfs调度器选择下一个调度进程的逻辑,由pick_next_task_fair函数来确定。
这一篇继续深入 pick_next_task_fair 函数的实现细节,重点分析 任务选择逻辑 、组调度优化 和 负载均衡触发机制。
1. 任务选择逻辑
为什么需要复杂的任务选择逻辑?
在 CFS 调度器中,任务选择不仅仅是一个简单的红黑树查找操作。由于引入了组调度(Group Scheduling)和运行时限制(Runtime Throttling),任务选择逻辑变得更加复杂。为了确保公平性和效率,调度器需要:
- 支持层级结构:处理任务组的嵌套关系;
- 动态调整运行时限制:避免某些任务组过度占用 CPU 时间;
- 优化上下文切换:减少不必要的调度实体更新。
任务选择逻辑是什么?
在 pick_next_task_fair 函数中,任务选择逻辑分为两个部分:简单路径 和 复杂路径。
简单路径
如果当前任务属于非公平调度类(如实时任务),或者系统未启用组调度,则直接进入简单路径:
c
if (!prev || prev->sched_class != &fair_sched_class)
goto simple;
在简单路径中,调度器通过 pick_next_entity 函数从根 cfs_rq 中选择下一个任务:
c
do {
se = pick_next_entity(cfs_rq, NULL);
set_next_entity(cfs_rq, se);
cfs_rq = group_cfs_rq(se);
} while (cfs_rq);
复杂路径
如果启用了组调度,并且当前任务属于公平调度类,则进入复杂路径。复杂路径的核心逻辑如下:
-
更新当前任务的运行时间:
cif (curr) { if (curr->on_rq) update_curr(cfs_rq); else curr = NULL; }- 如果当前任务仍在队列中,则更新其虚拟运行时间(
vruntime); - 否则,将其标记为不可用。
- 如果当前任务仍在队列中,则更新其虚拟运行时间(
-
检查运行时限制:
cif (unlikely(check_cfs_rq_runtime(cfs_rq))) { cfs_rq = &rq->cfs; if (!cfs_rq->nr_running) goto idle; goto simple; }- 如果当前
cfs_rq的运行时耗尽,则触发限制机制; - 如果没有可运行任务,则跳转到空闲路径。
- 如果当前
-
逐层选择任务:
cdo { se = pick_next_entity(cfs_rq, curr); cfs_rq = group_cfs_rq(se); } while (cfs_rq);- 从当前
cfs_rq中选择下一个任务; - 如果存在父级
cfs_rq,则继续向上遍历。
- 从当前
-
优化上下文切换:
cif (prev != p) { struct sched_entity *pse = &prev->se; while (!(cfs_rq = is_same_group(se, pse))) { int se_depth = se->depth; int pse_depth = pse->depth; if (se_depth <= pse_depth) { put_prev_entity(cfs_rq_of(pse), pse); pse = parent_entity(pse); } if (se_depth >= pse_depth) { set_next_entity(cfs_rq_of(se), se); se = parent_entity(se); } } put_prev_entity(cfs_rq, pse); set_next_entity(cfs_rq, se); }- 如果新任务与当前任务属于不同的调度实体,则仅更新受影响的部分;
- 避免对整个层级结构进行不必要的更新。
怎么用任务选择逻辑?
任务选择逻辑的核心用途是:
- 支持组调度:处理任务组的嵌套关系,确保每个任务组都能公平分配 CPU 时间;
- 动态调整运行时限制:避免某些任务组过度占用 CPU 时间;
- 优化上下文切换:减少不必要的调度实体更新,提升性能。
2. 组调度优化
为什么需要组调度优化?
在多任务并发环境中,任务组的调度可能会导致大量的上下文切换和层级更新。为了减少开销,调度器需要优化组调度的实现。
组调度优化是什么?
在 pick_next_task_fair 函数中,组调度优化主要体现在以下几个方面:
-
set_next_buddy偏好:- 在任务出队时,调度器会设置
next_buddy,引导调度器优先选择同一任务组中的任务; - 这种偏好可以减少跨任务组的上下文切换。
- 在任务出队时,调度器会设置
-
最小化层级更新:
- 如果新任务与当前任务属于同一任务组,则仅更新受影响的部分;
- 避免对整个层级结构进行不必要的更新。
-
运行时限制检查:
- 在选择任务时,调度器会动态检查任务组的运行时限制;
- 如果运行时耗尽,则触发限制机制。
怎么用组调度优化?
组调度优化的核心用途是:
- 减少上下文切换 :通过
set_next_buddy和最小化层级更新,降低调度开销; - 动态调整运行时限制:确保任务组之间的公平性;
- 提升性能:减少不必要的层级更新,提高调度效率。
3. 负载均衡触发机制
为什么需要负载均衡触发机制?
在多核系统中,CPU 资源的分配可能存在不均衡的情况。例如,某些 CPU 可能正在运行低优先级任务,而其他 CPU 则有高优先级任务等待调度。为了最大化系统性能,调度器需要及时发现并解决这种不均衡。
负载均衡触发机制是什么?
在 pick_next_task_fair 函数中,负载均衡触发机制通过以下代码实现:
c
idle:
if (!rf)
return NULL;
new_tasks = newidle_balance(rq, rf);
if (new_tasks < 0)
return RETRY_TASK;
if (new_tasks > 0)
goto again;
关键点解析:
-
newidle_balance:- 当 CPU 即将进入空闲状态时,调用
newidle_balance尝试从其他 CPU 拉取任务; - 如果成功拉取任务,则重新开始任务选择流程。
- 当 CPU 即将进入空闲状态时,调用
-
条件判断:
- 如果
new_tasks < 0,表示需要重试任务选择; - 如果
new_tasks > 0,表示成功拉取任务,重新开始任务选择。
- 如果
怎么用负载均衡触发机制?
负载均衡触发机制的核心用途是:
- 最大化 CPU 利用率:确保空闲的 CPU 不会长时间处于空闲状态;
- 提升系统响应速度:通过快速迁移高优先级任务,减少延迟;
- 动态平衡负载:根据实时负载变化,调整任务分布。
总结
在 pick_next_task_fair 函数中,任务选择逻辑 、组调度优化 和 负载均衡触发机制 分别解决了以下问题:
- 任务选择逻辑:支持组调度和运行时限制,确保公平性和效率;
- 组调度优化:减少上下文切换和层级更新,提升性能;
- 负载均衡触发机制:及时发现并解决 CPU 负载不均衡的问题,提升系统性能。
这些机制共同构成了 CFS 调度器的核心能力,确保了 Linux 内核在多任务并发环境下的高效性和稳定性。