Linux内核分析(调度类和调度实体)

news2024/12/27 3:24:44

文章目录

  • 前言
  • 一、调度类
      • 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
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2048507.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot依赖之Spring Data Redis 一 String类型

Spring Data Redis(一) 概念 Spring Data Redis (AccessDriver) 依赖名称: Spring Data Redis (AccessDriver)功能描述: Advanced and thread-safe Java Redis client for synchronous, asynchronous, and reactive usage. Supports Cluster, Sentinel, Pipelining, Auto-Re…

输入输出(I/0)流

一、 File: 是所有文件或者文件夹的路径抽象表现形式 file自动重写了toString方法,所以直接打印显示的是file内容 构造方法: public File(String pathname) public File(String parent,String child) public File(File parent,…

rust操作rabbitmq

Rust 操作 Rabbitmq 使用docker快速部署rabbitmq docker pull rabbitmq:management # 15672为rabbitmq 管理员端口,默认账号密码为guest(账号密码相同) docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:managementrust 添加amqp库lapin car…

影院订票系统/电影院售票系统/电影院购票系统的设计与实现/影院管理系统

摘 要 “互联网”的战略实施后,很多行业的信息化水平都有了很大的提升。但是目前很多电影院日常业务仍是通过人工管理的方式进行,需要在影院订票投入大量的人力进行很多重复性工作,这样就浪费了许多的人力物力,工作效率较低&…

【Godot4自学手册】第四十五节用着色器(shader)制作水中效果

本节内容,主要学习利用着色器制作水波纹效果,效果如下: 一、搭建新的场景 首先我们新建场景,根节点选择Node2D,命名为Water,给根节点添加两个Tilemap节点,一个命名为Background主要用于绘制地…

JUC介绍

一、并发与并行 1.并发 早期计算机CPU是单核的,为了提高CPU的利用率,减少等待时间,使用到了并发工作的理论 并发就是将CPU资源合理分配给多个任务,当一个任务执行I/O操作时,转去执行其他任务 2.并行 针对多核CPU&…

25届科大讯飞飞星计划 AI研究算法工程师 面经

目录 一面/技术面 2024/08/15 📋 总结: 本来应该是在7月底面试的,但因为有事就拖到了现在,或许是飞星计划里最晚面试的一批?面试官很和蔼,问的问题不算难,总体体验还算不错。 一面/技术面 2024/…

MySQL基础--逻辑存储结构,架构

逻辑存储结构 表空间(ibd 文件):一个 mysql 实例可以对应多个表空间,用于存储记录,索引等数据。 段:分为数据段,索引段,回滚段,InnoDB 是索引组织表,数据段就…

Unity引擎基础知识

目录 Unity基础知识概要 1. 创建工程 2. 工程目录介绍 3. Unity界面和五大面板 4. 游戏物体创建与操作 5. 场景和层管理 6. 组件系统 7. 脚本语言C# 8. 物理引擎和UI系统 学习资源推荐 Unity引擎中如何优化大型游戏项目的性能? Unity C#脚本语言的高级编…

修复 iPad 卡在准备更新或正在进行更新的问题

为什么iPad 更新卡住了?原因很难确定,因为 iPad 的许多故障和状况都可能导致 iPad 无法更新 iOS 和应用程序。此外,很难弄清楚这种情况持续了多长时间。但是,您不必太担心,因为这只是一个小案例,您可以阅读…

Java入门(上)

day01 - Java基础语法 1. 人机交互 1.1 什么是cmd? 就是在windows操作系统中,利用命令行的方式去操作计算机。 我们可以利用cmd命令去操作计算机,比如:打开文件,打开文件夹,创建文件夹等。 1.2 如何打…

单元训练13:串行接口的进阶应用

蓝桥杯,小蜜蜂,单元训练13:串行接口的进阶应用 /** Description:* Author: fdzhang* Email: zfdcqq.com* Date: 2024-08-17 15:41:34* LastEditTime: 2024-08-17 19:48:35* LastEditors: fdzhang*/ #include "stc15f2k60s2.h"#defi…

算法工程师第四十天(647. 回文子串 516.最长回文子序列 动态规划总结篇 )

参考文献 代码随想录 一、回文子串 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 示例 1: 输入:s "abc"…

MySQL 异步主从复制流程解析

前言: 首先MySQL主从复制方式有多种,包括 binlog、GTID等,这里基于 binlog 的形式,解析异步主从复制流程 首先通过下面命令查看全部 binlog 日志文件 show binary logs; binlog 日志文件如下: 然后查看其中一个文件…

ECMAScript6语法:默认参数和rest参数

1、默认参数 默认参数即在定义函数的参数列表中指定了默认值的参数。在 ES5 中,并没有提供在参数列表中指定参数默认值的语法,要想为函数的参数指定默认值,只能在函数体中实现,示例代码如下: function table(width, …

MBR10200FCT-ASEMI智能AI专用MBR10200FCT

编辑:ll MBR10200FCT-ASEMI智能AI专用MBR10200FCT 型号:MBR10200FCT 品牌:ASEMI 封装:TO-220F 批号:最新 最大平均正向电流(IF):10A 最大循环峰值反向电压(VRRM&a…

西安旅游系统--论文pf

TOC springboot383西安旅游系统--论文pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现,改变了几千年以来人们的生活,不仅仅是生活物资的丰富,还有精神层次的丰富。在互联网诞生之前,地域位置往往是人们思想上不可跨域的鸿…

YOLOv8侦测任务更换主干网络成MobileNetV3

目录 1. 添加主干网络模块 ​编辑1.1 在init.py中添加模块名 1.2 主体代码中添加调用语句块 2. 配置yaml文件 3. 修改成功 1. 添加主干网络模块 1.1 在init.py中添加模块名 1.2 主体代码中添加调用语句块 2. 配置yaml文件 3. 修改成功 自己随便找一个程序跑一跑验证…

微电网控制器是什么?微电网中央控制器|微电网协调控制器|微电网控制系统图|Micon2505微网中央控制器方案介绍

微电网控制器是什么?微电网中央控制器|微电网协调控制器|微电网控制系统图|Micon2505微网中央控制器方案介绍及其在油田采油机场景中的应用。微电网控制器广泛应用于具备光伏,储能,V2G,充电桩,风电,柴油发电…

图解内存分配算法 -- 小内存分配算法

图解内存分配算法 – 小内存分配算法 文章目录 图解内存分配算法 -- 小内存分配算法1. 算法介绍2. 算法图解2.1 约定2.2 数据结构介绍2.3 初始化2.4 第一次 malloc 40字节2.5 第二次 malloc 18 字节2.6 第三次 malloc 20字节2.7 第四次 malloc 40字节2.8 第一次 free2.9 第二次…