文章目录
- 前言
- 一、调度类
- 1. `stop_sched_class`
- 2. `dl_sched_class`
- 3. `rt_sched_class`
- 4. `fair_sched_class`
- 5. `idle_sched_class`
- 总结
- 二、调度类中的操作函数
- 三、调度实体
前言
调度是操作系统内核的一个关键职责,它涉及到如何合理分配CPU时间给不同的进程或线程。在Linux内核中,调度不仅仅是一个简单的任务切换过程,而是一个精细化管理的过程,涉及多个层次和多种策略。理解调度类(Scheduler Classes)和调度实体(Scheduling Entities)的工作机制,对于系统开发者和性能调优工程师来说,都是至关重要的。
一、调度类
在Linux内核中,调度类(sched_class
)是调度系统中的核心组件,它们决定了任务(进程或线程)如何被调度和管理。Linux内核支持多个调度类,每个调度类都具有不同的调度策略和行为,以满足不同的系统需求。以下是五个主要的调度类及其简要说明:
1. stop_sched_class
- 功能:
stop_sched_class
用于处理被停止的任务。它通常用于进程在被停止或挂起时的调度管理。这类任务不会被调度,主要用于保存进程的状态以便恢复。 - 特点:任务在这个调度类中不会被分配CPU时间。它的调度函数不会被调用,调度系统会忽略这个类的任务。
2. dl_sched_class
- 功能:
dl_sched_class
是"Deadline Scheduling"(截止时间调度)的调度类。它用于实时任务的调度,特别是那些有严格时间要求的任务。 - 特点:截止时间调度旨在确保任务在其截止时间之前完成。这个调度类使用基于截止时间的算法来决定任务的优先级,并确保实时任务的时效性。
3. rt_sched_class
- 功能:
rt_sched_class
是"Real-Time Scheduling"(实时调度)的调度类。它处理具有实时需求的任务,如音频处理或控制系统中的任务。 - 特点:实时调度类提供了几种不同的调度策略,如FIFO(先进先出)和RR(轮询),确保实时任务能够在可预测的时间内得到执行。这个类的任务通常具有比其他调度类更高的优先级。
4. fair_sched_class
- 功能:
fair_sched_class
是"Completely Fair Scheduler"(完全公平调度)的调度类。它用于处理普通任务的调度,试图在所有任务之间公平地分配CPU时间。 - 特点:完全公平调度(CFS)旨在提供一种公平的调度方式,以避免任务饥饿,并尽量均匀地分配CPU时间。CFS使用红黑树来跟踪任务的执行情况,并基于虚拟运行时间来决定调度顺序。
5. idle_sched_class
- 功能:
idle_sched_class
处理空闲任务。在系统没有其他任务需要调度时,空闲调度类负责运行系统空闲任务。 - 特点:这个调度类的任务通常是系统空闲的地方(idle task),用于在没有其他任务时降低CPU功耗。它的调度策略通常是低优先级的,以确保CPU在其他任务运行时能够被及时分配。
总结
每个调度类在Linux内核中扮演着不同的角色,从处理实时任务到公平分配CPU时间,再到处理系统空闲任务。调度类的设计和实现保证了系统能够有效地处理各种不同类型的任务,满足不同的性能和响应需求。理解这些调度类有助于深入掌握Linux内核的调度机制,并进行更好的系统优化和调试。
二、调度类中的操作函数
const struct sched_class fair_sched_class = {
/* 调度类的下一个调度类(idle_sched_class)*/
.next = &idle_sched_class,
/* 将任务加入调度队列的函数(公平调度队列) */
.enqueue_task = enqueue_task_fair,
/* 从调度队列中移除任务的函数(公平调度队列) */
.dequeue_task = dequeue_task_fair,
/* 任务自愿让出 CPU 的函数(公平调度) */
.yield_task = yield_task_fair,
/* 任务让出 CPU 给特定任务的函数(公平调度) */
.yield_to_task = yield_to_task_fair,
/* 检查当前任务是否需要被抢占的函数(公平调度) */
.check_preempt_curr = check_preempt_wakeup,
/* 选择下一个要调度的任务的函数(公平调度) */
.pick_next_task = __pick_next_task_fair,
/* 将上一个任务放入调度队列的函数(公平调度) */
.put_prev_task = put_prev_task_fair,
/* 设置下一个任务的函数(公平调度) */
.set_next_task = set_next_task_fair,
#ifdef CONFIG_SMP
/* 在多处理器系统中,平衡负载的函数(公平调度) */
.balance = balance_fair,
/* 选择任务的 CPU 运行队列的函数(公平调度) */
.select_task_rq = select_task_rq_fair,
/* 将任务迁移到新的 CPU 运行队列的函数(公平调度) */
.migrate_task_rq = migrate_task_rq_fair,
/* 处理 CPU 运行队列上线事件的函数(公平调度) */
.rq_online = rq_online_fair,
/* 处理 CPU 运行队列下线事件的函数(公平调度) */
.rq_offline = rq_offline_fair,
/* 处理任务死亡事件的函数(公平调度) */
.task_dead = task_dead_fair,
/* 设置任务允许使用的 CPU 的函数(公平调度) */
.set_cpus_allowed = set_cpus_allowed_common,
#endif
/* 处理任务时间片到期的函数(公平调度) */
.task_tick = task_tick_fair,
/* 处理任务创建事件的函数(公平调度) */
.task_fork = task_fork_fair,
/* 处理任务优先级改变事件的函数(公平调度) */
.prio_changed = prio_changed_fair,
/* 处理任务从一个任务切换到另一个任务的函数(公平调度) */
.switched_from = switched_from_fair,
/* 处理任务切换到另一个任务的函数(公平调度) */
.switched_to = switched_to_fair,
/* 获取轮询间隔的函数(公平调度) */
.get_rr_interval = get_rr_interval_fair,
/* 更新当前任务状态的函数(公平调度) */
.update_curr = update_curr_fair,
#ifdef CONFIG_FAIR_GROUP_SCHED
/* 处理任务组变化事件的函数(公平调度) */
.task_change_group = task_change_group_fair,
#endif
#ifdef CONFIG_UCLAMP_TASK
/* 是否启用任务 UClamp(用于任务 CPU 能力调整) */
.uclamp_enabled = 1,
#endif
};
主要函数解析:
enqueue_task_fair函数:
这个函数负责将一个新的或唤醒的任务添加到 CFS 的调度队列中
static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
struct cfs_rq *cfs_rq;
struct sched_entity *se = &p->se;
int idle_h_nr_running = task_has_idle_policy(p);
/*
* 将任务的预估利用率添加到 cfs_rq 的预估利用率中,
* 然后更新调度器的频率选择。
*/
util_est_enqueue(&rq->cfs, p);
/*
* 如果任务正在进行 I/O 等待,这里显式更新 CPU 频率利用率。
*/
if (p->in_iowait)
cpufreq_update_util(rq, SCHED_CPUFREQ_IOWAIT);
/* 遍历调度实体 */
for_each_sched_entity(se) {
/* 如果任务已经在运行队列中,则跳过 */
if (se->on_rq)
break;
/* 获取任务所属的 cfs_rq */
cfs_rq = cfs_rq_of(se);
/* 将任务实体加入到 cfs_rq 中 */
enqueue_entity(cfs_rq, se, flags);
/*
* 如果遇到被限制的 cfs_rq,则中止后续操作
* 注意:遇到被限制的 cfs_rq 时,我们将在后面增加最终的 h_nr_running 计数
*/
if (cfs_rq_throttled(cfs_rq))
break;
/* 更新 cfs_rq 的运行任务数量 */
cfs_rq->h_nr_running++;
cfs_rq->idle_h_nr_running += idle_h_nr_running;
/* 标记为唤醒操作 */
flags = ENQUEUE_WAKEUP;
}
/* 再次遍历调度实体 */
for_each_sched_entity(se) {
cfs_rq = cfs_rq_of(se);
cfs_rq->h_nr_running++;
cfs_rq->idle_h_nr_running += idle_h_nr_running;
if (cfs_rq_throttled(cfs_rq))
break;
/* 更新负载平均值 */
update_load_avg(cfs_rq, se, UPDATE_TG);
update_cfs_group(se);
}
/* 如果没有找到调度实体 */
if (!se) {
add_nr_running(rq, 1);
/*
* 新任务的初始 util_avg 设为 CPU 空闲容量的一半,
* 对于小任务,可能会跨越超负荷阈值,这会影响负载均衡器的任务放置。
* 为了缓解这个问题,不在超负荷状态检测中计入新任务的第一次入队操作。
*/
if (flags & ENQUEUE_WAKEUP)
update_overutilized_status(rq);
}
/* 如果启用了带宽控制 */
if (cfs_bandwidth_used()) {
/*
* 带宽控制启用时,上述操作中断可能会导致叶子列表维护不完全,
* 触发以下断言。
*/
for_each_sched_entity(se) {
cfs_rq = cfs_rq_of(se);
if (list_add_leaf_cfs_rq(cfs_rq))
break;
}
}
assert_list_leaf_cfs_rq(rq);
/* 更新高精度定时器 */
hrtick_update(rq);
}
dequeue_task_fair函数:
将任务从 CFS 调度队列中移除
static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
struct cfs_rq *cfs_rq;
struct sched_entity *se = &p->se;
int task_sleep = flags & DEQUEUE_SLEEP; // 检查任务是否因休眠而被删除
int idle_h_nr_running = task_has_idle_policy(p); // 任务是否有空闲策略
bool was_sched_idle = sched_idle_rq(rq); // 检查之前的调度队列是否为空闲队列
// 遍历任务的调度实体,进行任务的移除
for_each_sched_entity(se) {
cfs_rq = cfs_rq_of(se); // 获取调度队列
dequeue_entity(cfs_rq, se, flags); // 从调度队列中移除任务
/*
* 如果遇到被限制的 cfs_rq(调度队列当前受限,不能接受更多任务),
* 则中止后续操作。
*/
if (cfs_rq_throttled(cfs_rq))
break;
// 更新运行任务的数量
cfs_rq->h_nr_running--;
cfs_rq->idle_h_nr_running -= idle_h_nr_running;
// 如果调度队列还有其他实体,处理父实体
if (cfs_rq->load.weight) {
// 避免对当前实体进行重新负载评估
se = parent_entity(se);
/*
* 如果任务在其调度时间片内处于休眠状态,且没有被限制,
* 则调整下一个选择的实体以偏向当前 cfs_rq。
*/
if (task_sleep && se && !throttled_hierarchy(cfs_rq))
set_next_buddy(se);
break;
}
flags |= DEQUEUE_SLEEP; // 设置 DEQUEUE_SLEEP 标志
}
// 遍历任务的调度实体,更新调度队列
for_each_sched_entity(se) {
cfs_rq = cfs_rq_of(se);
cfs_rq->h_nr_running--;
cfs_rq->idle_h_nr_running -= idle_h_nr_running;
if (cfs_rq_throttled(cfs_rq))
break;
// 更新负载平均值和 cfs 组
update_load_avg(cfs_rq, se, UPDATE_TG);
update_cfs_group(se);
}
// 如果没有其他调度实体,更新运行任务的总数
if (!se)
sub_nr_running(rq, 1);
// 如果之前的调度队列非空闲且当前是空闲队列,则提前平衡调度
if (unlikely(!was_sched_idle && sched_idle_rq(rq)))
rq->next_balance = jiffies;
// 更新利用率信息
util_est_dequeue(&rq->cfs, p, task_sleep);
// 更新高精度定时器
hrtick_update(rq);
}
yield_task_fair函数:
在公平调度器中处理任务的主动让渡
static void yield_task_fair(struct rq *rq)
{
struct task_struct *curr = rq->curr; // 当前正在运行的任务
struct cfs_rq *cfs_rq = task_cfs_rq(curr); // 当前任务所属的 CFS 调度队列
struct sched_entity *se = &curr->se; // 当前任务的调度实体
/*
* 如果调度队列中只有当前任务,直接返回。
* 这意味着没有其他任务需要调度或执行。
*/
if (unlikely(rq->nr_running == 1))
return;
// 清除与当前任务相关的 buddy 标记(可能是调度优化相关的标记)
clear_buddies(cfs_rq, se);
// 如果当前任务不是批处理任务,则更新运行时统计信息
if (curr->policy != SCHED_BATCH) {
// 更新调度队列的时间戳
update_rq_clock(rq);
/*
* 更新当前任务的运行时间统计信息。
* 这是为了保持当前任务的运行时间数据的准确性。
*/
update_curr(cfs_rq);
/*
* 通知 `update_rq_clock()` 函数,已经进行了更新,
* 以避免在 `schedule()` 函数中进行额外的微小更新时间,
* 这样可以避免调度路径的双倍开销。
*/
rq_clock_skip_update(rq);
}
// 将当前任务标记为跳过 buddy 调度,这可能是调度优化的一部分
set_skip_buddy(se);
}
.pick_next_task = __pick_next_task_fair,
.put_prev_task = put_prev_task_fair,
.set_next_task = set_next_task_fair,
__pick_next_task_fair
- 作用:选择下一个任务进行调度。
- 说明:这是公平调度器(CFS)的核心函数之一,用于从调度队列中选择下一个要执行的任务。它根据任务的优先级和运行时间等因素,决定哪个任务应当获得 CPU 时间。这一过程涉及到对任务的权重、运行时间等参数的评估,以保证系统的公平性。
put_prev_task_fair
- 作用:处理当前任务(之前的任务)的状态更新。
- 说明:在任务切换之前调用,用于处理当前任务的状态。主要任务是更新当前任务的运行时间数据和调度队列的状态。具体来说,它可能会更新任务的调度实体信息(如运行时间、优先级等),以便公平调度器在下一次调度时能更准确地评估任务的状态。
set_next_task_fair
- 作用:设置下一个任务的状态。
- 说明:在调度过程中,设置下一个要执行的任务的相关状态。这个函数负责初始化或配置即将调度的任务,以便它能顺利地开始执行。它可能会涉及到更新任务的调度实体状态以及其他调度相关的设置。
这些函数共同协作,确保公平调度器能高效且准确地管理任务的调度,保证系统资源的合理分配。
三、调度实体
在每一个task_struct结构体中都会有调度实体:
struct sched_entity 是 Linux 内核调度器中用于描述调度实体(任务或任务组)的数据结构。调度实体可以是一个单独的任务,也可以是一个任务组(当启用组调度时)。它的作用是跟踪调度相关的状态信息和统计数据。
struct sched_entity {
/* For load-balancing: */
struct load_weight load; // 任务的负载权重,用于负载均衡
unsigned long runnable_weight; // 任务的可运行权重
struct rb_node run_node; // 红黑树节点,用于调度队列管理
struct list_head group_node; // 任务组的链表节点(当启用组调度时)
unsigned int on_rq; // 任务是否在运行队列上(标志位)
u64 exec_start; // 任务开始执行的时间戳
u64 sum_exec_runtime; // 任务自创建以来总的执行时间
u64 vruntime; // 虚拟运行时间,用于公平调度
u64 prev_sum_exec_runtime; // 上一个时间片结束时的执行时间总和
u64 nr_migrations; // 任务迁移次数
struct sched_statistics statistics; // 任务的调度统计信息
#ifdef CONFIG_FAIR_GROUP_SCHED
int depth; // 任务组的深度,用于层次调度
struct sched_entity *parent; // 任务组的父任务(或任务组)
/* rq on which this entity is (to be) queued: */
struct cfs_rq *cfs_rq; // 当前调度实体所在的 CFS 运行队列
/* rq "owned" by this entity/group: */
struct cfs_rq *my_q; // 当前调度实体(或任务组)拥有的 CFS 运行队列
#endif
#ifdef CONFIG_SMP
/*
* Per entity load average tracking.
*
* Put into separate cache line so it does not
* collide with read-mostly values above.
*/
struct sched_avg avg; // 每个调度实体的负载平均值,用于平衡多处理器系统中的负载
#endif
};