【Linux内核二十五】进程管理模块:CFS调度器pick_next_task_fair(一):pick_next_task_fair方法

接上篇:【Linux内核二十四】进程管理模块:CFS调度器dequeue_task_fair方法(二)

入队的enqueue和出队的dequeue都说完了,现在继续下一个。

来说说cfs调度器选择下一个调度进程的逻辑,由pick_next_task_fair函数来确定。

这一篇继续深入 pick_next_task_fair 函数的实现细节,重点分析 任务选择逻辑组调度优化负载均衡触发机制


1. 任务选择逻辑

为什么需要复杂的任务选择逻辑?

在 CFS 调度器中,任务选择不仅仅是一个简单的红黑树查找操作。由于引入了组调度(Group Scheduling)和运行时限制(Runtime Throttling),任务选择逻辑变得更加复杂。为了确保公平性和效率,调度器需要:

  1. 支持层级结构:处理任务组的嵌套关系;
  2. 动态调整运行时限制:避免某些任务组过度占用 CPU 时间;
  3. 优化上下文切换:减少不必要的调度实体更新。

任务选择逻辑是什么?

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);

复杂路径

如果启用了组调度,并且当前任务属于公平调度类,则进入复杂路径。复杂路径的核心逻辑如下:

  1. 更新当前任务的运行时间

    c 复制代码
    if (curr) {
        if (curr->on_rq)
            update_curr(cfs_rq);
        else
            curr = NULL;
    }
    • 如果当前任务仍在队列中,则更新其虚拟运行时间(vruntime);
    • 否则,将其标记为不可用。
  2. 检查运行时限制

    c 复制代码
    if (unlikely(check_cfs_rq_runtime(cfs_rq))) {
        cfs_rq = &rq->cfs;
    
        if (!cfs_rq->nr_running)
            goto idle;
    
        goto simple;
    }
    • 如果当前 cfs_rq 的运行时耗尽,则触发限制机制;
    • 如果没有可运行任务,则跳转到空闲路径。
  3. 逐层选择任务

    c 复制代码
    do {
        se = pick_next_entity(cfs_rq, curr);
        cfs_rq = group_cfs_rq(se);
    } while (cfs_rq);
    • 从当前 cfs_rq 中选择下一个任务;
    • 如果存在父级 cfs_rq,则继续向上遍历。
  4. 优化上下文切换

    c 复制代码
    if (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);
    }
    • 如果新任务与当前任务属于不同的调度实体,则仅更新受影响的部分;
    • 避免对整个层级结构进行不必要的更新。

怎么用任务选择逻辑?

任务选择逻辑的核心用途是:

  1. 支持组调度:处理任务组的嵌套关系,确保每个任务组都能公平分配 CPU 时间;
  2. 动态调整运行时限制:避免某些任务组过度占用 CPU 时间;
  3. 优化上下文切换:减少不必要的调度实体更新,提升性能。

2. 组调度优化

为什么需要组调度优化?

在多任务并发环境中,任务组的调度可能会导致大量的上下文切换和层级更新。为了减少开销,调度器需要优化组调度的实现。

组调度优化是什么?

pick_next_task_fair 函数中,组调度优化主要体现在以下几个方面:

  1. set_next_buddy 偏好

    • 在任务出队时,调度器会设置 next_buddy,引导调度器优先选择同一任务组中的任务;
    • 这种偏好可以减少跨任务组的上下文切换。
  2. 最小化层级更新

    • 如果新任务与当前任务属于同一任务组,则仅更新受影响的部分;
    • 避免对整个层级结构进行不必要的更新。
  3. 运行时限制检查

    • 在选择任务时,调度器会动态检查任务组的运行时限制;
    • 如果运行时耗尽,则触发限制机制。

怎么用组调度优化?

组调度优化的核心用途是:

  1. 减少上下文切换 :通过 set_next_buddy 和最小化层级更新,降低调度开销;
  2. 动态调整运行时限制:确保任务组之间的公平性;
  3. 提升性能:减少不必要的层级更新,提高调度效率。

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;

关键点解析:

  1. newidle_balance

    • 当 CPU 即将进入空闲状态时,调用 newidle_balance 尝试从其他 CPU 拉取任务;
    • 如果成功拉取任务,则重新开始任务选择流程。
  2. 条件判断

    • 如果 new_tasks < 0,表示需要重试任务选择;
    • 如果 new_tasks > 0,表示成功拉取任务,重新开始任务选择。

怎么用负载均衡触发机制?

负载均衡触发机制的核心用途是:

  1. 最大化 CPU 利用率:确保空闲的 CPU 不会长时间处于空闲状态;
  2. 提升系统响应速度:通过快速迁移高优先级任务,减少延迟;
  3. 动态平衡负载:根据实时负载变化,调整任务分布。

总结

pick_next_task_fair 函数中,任务选择逻辑组调度优化负载均衡触发机制 分别解决了以下问题:

  1. 任务选择逻辑:支持组调度和运行时限制,确保公平性和效率;
  2. 组调度优化:减少上下文切换和层级更新,提升性能;
  3. 负载均衡触发机制:及时发现并解决 CPU 负载不均衡的问题,提升系统性能。

这些机制共同构成了 CFS 调度器的核心能力,确保了 Linux 内核在多任务并发环境下的高效性和稳定性。

相关推荐
我是一个对称矩阵3 小时前
分区安装Ubuntu系统
linux·运维·ubuntu
小捏哩3 小时前
死锁检测组件的设计
linux·网络·数据结构·c++·后端
mzhan0173 小时前
Linux: sched: pick_next_task_fair 这个函数的功能
linux·运维·算法
认真的薛薛3 小时前
JVM和pod内存关系
linux·运维·jvm
色空大师4 小时前
【网站开发-java】
java·linux·服务器·开发语言·网站·搭建网站
释怀不想释怀4 小时前
硬盘基础了解知识
linux·运维·服务器
福楠4 小时前
现代C++ | 右值引用 + std::move + noexcept
linux·c语言·开发语言·c++
逆向编程4 小时前
Ubuntu 入门教程,日常常用命令
linux·运维·ubuntu
逆向编程5 小时前
Ubuntu 入门教程:从安装到日常使用,新手一步到位
linux·运维·ubuntu