《Linux内核源码分析》(3)调度器及CFS调度器
《Linux内核源码分析》(3)调度器及CFS调度器
文章目录
- 《Linux内核源码分析》(3)调度器及CFS调度器
- 一、调度器
- 1、调度器
- 2、调度类sched_class结构体
- 3、优先级
- 4、内核调度策略
- 二、CFS调度器
- 1、CFS调度器基本原理
- 2、调度子系统各个组件模块
- 3、CFS调度器就绪队列内核源码
一、调度器
1、调度器
Linux
内核中用来安排调度进程(一段程序的执行过程)执行的模块称为调度器(Scheduler
),它可以切换进程状态(Process status
)。比如:执行、可中断睡眠、不可中断睡眠、退出、暂停等。
- 调度器相当于CPU中央处理器的管理员,主要负责完成做两件事情:
- 选择某些就绪进程来执行
- 打断某些执行的进程让它们变为就绪状态
2、调度类sched_class结构体
-
调度器类提供了通用调度器和各个调度方法之间的关联,Linux内核抽象一个调度类
struct sched_class
结构体表示调度类,具体内核源码如下:
kernel/sched/<sched.h>struct sched_class {/*系统当中有多个调度类,按照调度优先级排成一个链表,下一优先级的高类*/const struct sched_class *next;#ifdef CONFIG_UCLAMP_TASKint uclamp_enabled; #endif/*将进程加入到执行队列当中,即将调度实体(进程)存放到红黑树中,并对nr_running变量自动会加1。(nr_running指定了队列上可运行进程的数目,不考虑其优先级或调度类)*/void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);/*从执行队列当中删除进程,并对nr_running变量自动减1 */void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);/*放弃CPU执行权,实际上该函数执行先出队后入队,在这种情况下,它直接将调度实体放在红黑树的最右端*/void (*yield_task) (struct rq *rq);bool (*yield_to_task)(struct rq *rq, struct task_struct *p, bool preempt);/*用于检杳当前进程是否可被新进程抢占*/void (*check_preempt_curr)(struct rq *rq, struct task_struct *p, int flags);/*选择下一个应用要运行的进程*/struct task_struct *(*pick_next_task)(struct rq *rq);/*将进程放回到运行队列当中*/void (*put_prev_task)(struct rq *rq, struct task_struct *p);void (*set_next_task)(struct rq *rq, struct task_struct *p, bool first);#ifdef CONFIG_SMPint (*balance)(struct rq *rq, struct task_struct *prev, struct rq_flags *rf);/*为进程选择一个合适的CPU */int (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);/*迁移任务到处一个CPU */void (*migrate_task_rq)(struct task_struct *p, int new_cpu);/*专门用于唤醍进程*/void (*task_woken)(struct rq *this_rq, struct task_struct *task);/*修改进程在CPU的亲和力*/void (*set_cpus_allowed)(struct task_struct *p,const struct cpumask *newmask);/*启动运行队列*/void (*rq_online)(struct rq *rq);/*禁止运行队列*/void (*rq_offline)(struct rq *rq); #endif/*调用自time tick函数,它可能引起进程切换,将驱动运行时(running)抢占*/void (*task_tick)(struct rq *rq, struct task_struct *p, int queued);/* 进程创建时调用,不同调度策略的进程初始化也是不一样的 */void (*task_fork)(struct task_struct *p);/*进程退出时会使用*/void (*task_dead)(struct task_struct *p);/** The switched_from() call is allowed to drop rq->lock, therefore we* cannot assume the switched_from/switched_to pair is serliazed by* rq->lock. They are however serialized by p->pi_lock.*//*专门用于进程切换操作*/void (*switched_from)(struct rq *this_rq, struct task_struct *task);void (*switched_to) (struct rq *this_rq, struct task_struct *task);/*更改进程的优先级*/void (*prio_changed) (struct rq *this_rq, struct task_struct *task,int oldprio);unsigned int (*get_rr_interval)(struct rq *rq,struct task_struct *task);void (*update_curr)(struct rq *rq);#define TASK_SET_GROUP 0 #define TASK_MOVE_GROUP 1#ifdef CONFIG_FAIR_GROUP_SCHEDvoid (*task_change_group)(struct task_struct *p, int type); #endif };
-
调度器类可分为:
stop_sched_class
、dl_sched_class
、rt_sched_class
、fair_sched_class
和idle_sched_class
kernel/sched/<sched.h>extern const struct sched_class stop_sched_class;//停机调度类 extern const struct sched_class dl_sched_class;//限期调度类 extern const struct sched_class rt_sched_class;//实时调度类 extern const struct sched_class fair_sched_class;//公平调度类 extern const struct sched_class idle_sched_class;//空闲调度类
这5种调度类的优先级从高到低依次为:停机调度类、限期调度类、实时调度类、公平调度类、空闲调度类。其中,
SCHED_NORMAL
、SCHED_BATCH
和SCHED_IDLE
直接被映射到fair_sched_class
;SCHED_FIFO
和SCHED_RR
与rt_sched_class
向关联。Linux
调度核心选择下一个合适的task
运行时,会按照优先级顺序遍历调度类的pick_next_task
函数- 停机调度类:优先级是最高的调度类,停机进程是优先级最高的进程,可以抢占所有其它进程,其他进程不可能抢占停机进程.
const struct sched_class stop_sched_class = {.next = &dl_sched_class,.enqueue_task = enqueue_task_stop,.dequeue_task = dequeue_task_stop,.yield_task = yield_task_stop,.check_preempt_curr = check_preempt_curr_stop,.pick_next_task = pick_next_task_stop,.put_prev_task = put_prev_task_stop,.set_next_task = set_next_task_stop,... };
- 限期调度类:最早使用优先算法,使用红黑树把进程按照绝对截止期限从小到大排序,每次调度时选择绝对截止期限最小的进程。
const struct sched_class dl_sched_class = {.next = &rt_sched_class,.enqueue_task = enqueue_task_dl,.dequeue_task = dequeue_task_dl,.yield_task = yield_task_dl,.check_preempt_curr = check_preempt_curr_dl,.pick_next_task = pick_next_task_dl,.put_prev_task = put_prev_task_dl,.set_next_task = set_next_task_dl,... };
- 实时调度类:为每个调度优先级维护一个队列。
const struct sched_class rt_sched_class = {.next = &fair_sched_class,.enqueue_task = enqueue_task_rt,.dequeue_task = dequeue_task_rt,.yield_task = yield_task_rt,.check_preempt_curr = check_preempt_curr_rt,.pick_next_task = pick_next_task_rt,.put_prev_task = put_prev_task_rt,.set_next_task = set_next_task_rt,... };
- 公平调度类:使用完全公平调度算法。完全公平调度算法引入虚拟运行时间的相关概念:
虚拟运行时间 = 实际运行时间 * nice为0对应的权重 / 进程的权重
。const struct sched_class fair_sched_class = {.next = &idle_sched_class,.enqueue_task = enqueue_task_fair,.dequeue_task = dequeue_task_fair,.yield_task = yield_task_fair,.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,... };
- 空闲调度类:每个CPU上有一个空闲线程,即
0
号线程。空闲调度类优先级最低,仅当没有其他进程可以调度的时候,才会调度空闲线程。const struct sched_class idle_sched_class = {/* .next is NULL *//* no enqueue/yield_task for idle tasks *//* dequeue is not valid, we print a debug message there: */.dequeue_task = dequeue_task_idle,.check_preempt_curr = check_preempt_curr_idle,.pick_next_task = pick_next_task_idle,.put_prev_task = put_prev_task_idle,.set_next_task = set_next_task_idle,... };
- 停机调度类:优先级是最高的调度类,停机进程是优先级最高的进程,可以抢占所有其它进程,其他进程不可能抢占停机进程.
-
观察上述5个调度类的
next
成员变量,可以发现他们是串联在一起的。Linux
调度核心选择下一个合适的task
运行时,会按照优先级顺序遍历调度类的pick_next_task
函数。
3、优先级
- 在用户空间可以通过
nice
命令设置进程的静态优先级,这在内部会调用nice
系统调用。进程的nice
值为[-20,+19]
。值越低,表明优先级越高。内核使用范围[0,139]
来表示内部优先级。同样是值越低,优先级越高。实时进程范围为[0,99]
。nice
值[-20, +19]
映射到范围100
到139
。如下图所示,实时进程的优先级总是比普通进程更高。
- Linux内核优先级源码如下:
include/linux/sched/<prio.h>#define MAX_NICE 19 #define MIN_NICE -20/*nice值的范围*/ #define NICE_WIDTH (MAX_NICE - MIN_NICE + 1)/*实时进程最大优先级(不包含)*/ #define MAX_USER_RT_PRIO 100 #define MAX_RT_PRIO MAX_USER_RT_PRIO/*普通进程最大优先级(不包含)*/ #define MAX_PRIO (MAX_RT_PRIO + NICE_WIDTH) // 140#define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2)
4、内核调度策略
struct task_struct
中有成员变量unsigned int policy
,指代保存进程的调度策略。Linux
内核提供一些调度策略供用户应用程序来选择调度器。Linux
内核调度策略源码分析如下:
include/uapi/linux/<sched.h>/** Scheduling policies*//*用于普通进程,通过CFS调度器实现。*/ #define SCHED_NORMAL 0/* 先进先出调度算法(实时调度策略),相同优先级任务先到先服务,高优先级的任务可以抢占低优先级的任务 */ #define SCHED_FIFO 1/*轮流调度算法(实时调度策略)*/ #define SCHED_RR 2/*用于非交互处理器消耗型进程,相当于SCHED_NORMAL分化版本,采用分时策略,根据动态优先级,分配CPU运行需要资源*/ #define SCHED_BATCH 3/* SCHED_ISO: reserved but not implemented yet *//*普通迸程调度策略,使task以最低优先级选择CFS调度器来调度运行*/ #define SCHED_IDLE 5/*限期进程调度策略,使task选择Deadline调度器来调度运行*/ #define SCHED_DEADLINE 6
- 备注:其中
stop
调度器和DLE-task
调度器,仅使用于内核,用户没有办法进行选择。
- 备注:其中
二、CFS调度器
1、CFS调度器基本原理
- 完全公平调度算法体现在对待每个进程都是公平的,让每个进程都运行—段相同的时间片,这就是基于时间片轮询调度算法。
CFS
定义一种新调度模型,它给cfs_rq
(cfs
的run queue
)中的每一个进程都设置一个虚拟时钟:vruntime(virtual runtime)
。如果一个进程得以执行,随着执行时间的不断增长,其vruntime
也将不断增大,没有得到执行的进程vruntime
将保持不变。 - 下图说明了调度器如何记录哪个进程已经等待了多长时间。由于可运行进程是排队的,该结构称之为就绪队列
所有的可运行进程都按时间在一个红黑树中排序,所谓时间即其等待时间。等待CPU时间最长的进程是最左侧的项,调度器下一次会考虑该进程。等待时间稍短的进程在该树上从左至右排序。在一个调度周期里面,所有进程的虚拟运行时间是相同的,所以在进程调度时,只需要找到虚拟运行时间最小的进程调度运行即可。 - 实现完全公平调度算法,要为进程定义两个时间
-
实际运行时间
实际运行时间 =调度周期 * 进程权重 / 所有进程权重之和
调度周期:指所有进程运行一遍所需要的时间;
进程权重:根据进程的重要性,分配给每个进程不同的权重eg:调度周期为
60ms
,进程A
的权重为1
,而且进程B
的权重为2
,那么:
进程A
实际运行时间为:60ms * 1/(1 +2)=20ms
进程B
实际运行时间为:60ms * 2/(1 +2)=40ms
-
虚拟运行时间
虚拟运行时间 =实际运行时间 * nice为0对应的权重(1024) / 进程的权重
=(调度周期 * 进程权重 / 所有进程权重之和 * 1024 / 进程权重
=调度周期 * 1024 / 所有进程总权重
。
-
2、调度子系统各个组件模块
- 调度器通过各个组件模块及一系列数据结构,来排序和管理系统中的进程。它们之间关系如下:
- 主调度器:通过调用
schedule()
函数来完成进程的选择和切换。 - 周期性调度器:根据固定频率自动调用
scheduler_tick
函数,不时检测是否有必要进行进程切换。 - 上下文切换:主要做两个事情(切换地址空间、切换寄存器和栈空间)
- 主调度器:通过调用
3、CFS调度器就绪队列内核源码
struct cfs_rq {/*CFS运行队列中所有进程总负我*/struct load_weight load;unsigned long runnable_weight;/*cfs_rq中调度实体数量*/unsigned int nr_running;unsigned int h_nr_running; /* SCHED_{NORMAL,BATCH,IDLE} */unsigned int idle_h_nr_running; /* SCHED_IDLE */u64 exec_clock;u64 min_vruntime;
#ifndef CONFIG_64BITu64 min_vruntime_copy;
#endif/*红黑树的root*/struct rb_root_cached tasks_timeline;/*下一个调度结点(红黑树最左边结点就是下个调度实体)*/struct rb node *rb leftmost;...
相关文章:

《Linux内核源码分析》(3)调度器及CFS调度器
《Linux内核源码分析》(3)调度器及CFS调度器 文章目录 《Linux内核源码分析》(3)调度器及CFS调度器一、调度器1、调度器2、调度类sched_class结构体3、优先级4、内核调度策略 二、CFS调度器1、CFS调度器基本原理2、调度子系统各个组件模块3、CFS调度器就绪队列内核源码 一、调度…...

Docker:如何删除已存在的镜像
要删除已存在的 Docker 镜像,您可以使用 docker rmi 命令。 以下是完整的流程 步骤1:停止容器 如容器正在运行需要停止正在运行的 Docker 容器,您可以使用 docker stop 命令。 以下是停止容器的步骤: 首先,使用 do…...

Qt——Qt 开发中所涉及的所有控件(基本控件、容器控件、布局控件、高级控件、其他控件、多媒体控件、定制控件)
Qt 开发中所涉及的所有控件 一、基本控件 二、容器控件 三、布局控件 四、高级控件 五、其他控件 六、多媒体控件 七、定制控件 Qt开发中提供了许多控件(Widgets)供开发者使用,用于构建图形用户界面(GUI)应用程序。以…...

基于Ubuntu坏境下的Suricata坏境搭建
目录 Suricata环境安装 第一步、在 Ubuntu 端点安装 Suricata 1、加入Suricata源 2、更新安装包 3、下载SuricataSuricata 第二步、下载并提取新兴威胁 Suricata 规则集 1、在tmp文件夹下载 Suricata 规则集 如果发现未安装curl,使用apt安装即可:…...

vue3权限管理——(路由权限)动态路由设置
1.大概思路 设置基础路由login和home等页面;登录后从后端获取user,token,rights等数据,并将数据同时存储到vuex和sessionStorage中将后端获取的权限数据(作为不同用户显示不同菜单及不同路由的依据)和路由页面进行映射࿱…...

小程序开发之登录授权
小程序开发登录授权流程 看懂这张图登录授权就没问题了(哈哈哈哈哈) 说明: 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 sess…...

批量根据excel数据绘制折线图
要批量根据Excel数据绘制折线图,可以使用数据处理和图表绘制软件,例如Microsoft Excel或Python中的Matplotlib库。以下是两种方法: 1. 使用Microsoft Excel: - 打开Excel并导入包含数据的工作表。 - 选择需要绘制折线图的数…...

无锁并发:探秘CAS机制的魔力
😊 作者: 一恍过去 💖 主页: https://blog.csdn.net/zhuocailing3390 🎊 社区: Java技术栈交流 🎉 主题: 无锁并发:探秘CAS机制的魔力 ⏱️ 创作时间: 2…...

iOS App签名与重签名:从开发者证书到重新安装运行
前文回顾: iOS脱壳技术(二):深入探讨dumpdecrypted工具的高级使用方法 iOS逆向:越狱及相关概念的介绍 在本文中,我们将详细介绍iOS应用的签名过程,包括开发者证书的种类、证书与App ID、Provisi…...

vue项目,如何修改Element-Plus等UI组件库的样式,三种方式搞定!!!
前言 我们在学习和使用组件库构建页面的时候,时常会遇到这样的问题。 即,尽管组件库已经提供了较多的功能,来帮助我们构建自定义的效果,但有时仍不能使我们满意。 这个时候我们就不得不修改UI库的样式,来达到想要的状…...

httpd协议与apache
1.http 相关概念 HTTP是处于应用层的协议,使用TCP传输层协议进行可靠的传送。因此,需要特别提醒的是,万维网是基于因特网的一种广泛因特网应用系统,且万维网采用的是HTTP(80/TCP)和 HTTPS(443/…...

Go 自学:文件的写入和读取
首先,使用os.Create()函数建立一个文件。 接着,使用io.WriteString()函数将内容写入文件。 最后,使用os.ReadFile()函数读取文件内容。 注意,这里读取的文件内容是data byte,我们需要使用string()函数将其转换为字符串…...

py 项目上线centos
1 服务器py版本 ps -ef|grep python|grep -v grep 2 2.x版本 安装 PyMySQL pip install PyMySQL0.9.3 3 后台运行py文件 nohup python down.py 1 > log.log 2>&1 & 这个命令将 down.py 程序放入后台运行, 同时将 stdout 输出到 log.log 文件中&…...

【git】would clobber existing tag 报错解决
问题 在用vscode的Git去pull代码的时候git弹窗报错,查看报错日志发现以下内容: > git pull --tags origin feature/xxx-2.0.0 From 173.110.11.22:VV-WORK-FE/vv-desktop* branch feature/xxx-2.0.0 -> FETCH_HEAD! [rejected] …...

Python OCR 使用easyocr库将图片中的文章提取出来
Python OCR 使用easyocr库将图片中的文章提取出来 初环境内容步骤一:安装easyocr库步骤二:导入必要的库步骤三:创建OCR阅读器对象步骤四:指定要识别的图片路径步骤五:执行OCR识别并提取文章内容步骤六:遍历…...

门禁系统忘记登入密码,现在更换电脑如何迁移旧电脑门禁系统的数据
环境: ivms-4200 v3.10.0.6_c 问题描述: 门禁系统忘记登入密码,现在更换电脑如何迁移旧电脑门禁系统的数据,旧电脑记住密码,忘了密码和密保了 解决方案: 1.前往海康官网下载4200客户端,在新电脑上安装 …...

初试Eureka注册中心
Eureka是spring cloud中的一个负责服务注册与发现的组件。遵循着CAP理论中的A(可用性)P(分区容错性)。一个Eureka中分为eureka server和eureka client。其中eureka server是作为服务的注册与发现中心。 搭建eureka服务 引入eureka依赖 引入SpringCloud为eureka提供的starter依…...

【趣味随笔】怎么维护自己的电脑?
📢:如果你也对机器人、人工智能感兴趣,看来我们志同道合✨ 📢:不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 📢:文章若有幸对你有帮助,可点赞 👍…...

element 下拉组件获取对象
// 选择数据user:[{name:"小白",id:1,money:"100",love:"蛋糕"},{name:"小黑",id:2,money:"200",love:"奶茶"},{name:"小红",id:3,money:"300",love:"烧烤"},] <div><el…...

IDEA下SpringBoot指定环境、配置文件启动
1、idea下的SpringBoot启动:指定配置文件 Springboot项目有如下配置文件 主配置文件application.yml, 测试环境:application-test.yml 生产环境:application-pro.yml 开发环境:application-dev.yml 1.1.配置文件…...

python可视化matplotlib——绘制正弦和余弦
这是一个使用matplotlib库绘制正弦和余弦函数曲线的代码示例。代码中导入了需要的库,并设置了x轴和y轴的标签字体为华文楷体。然后,使用numpy生成一组x轴上的值t,并使用正弦函数生成对应的y轴值s,再使用余弦函数生成对应的y轴值z。…...

Day48|leetcode 198.打家劫舍、213.打家劫舍II、打家劫舍|||
leetcode 198.打家劫舍 题目链接:198. 打家劫舍 - 力扣(LeetCode) 视频链接:动态规划,偷不偷这个房间呢?| LeetCode:198.打家劫舍_哔哩哔哩_bilibili 题目概述 你是一个专业的小偷,…...

Mysql001:Mysql概述以及安装
前言:本课程将从头学习Mysql,以我的工作经验来说,sql语句真的太重要的,现在互联网所有的一切都是建立在数据上,因为互联网的兴起,现在的数据日月增多,每年都以翻倍的形式增长,对于数…...

如何调用api接口获取到商品数据
要调用API接口获取商品数据,需要进行以下步骤: 1.确定API接口 首先需要确定要使用的API接口,可以通过搜索引擎或者相关文档来查找适合的API接口。以淘宝开放平台为例,可以使用淘宝的商品信息查询API接口来获取商品数据。 2.注册…...

http请求方式过滤器与拦截器的区别
get:获取查询数据(查询)post:数据的提交,新增操作(增加)put:向服务端发送数据、改变信息,侧重点在于对数据的修改操作delete:数据库数据的删除head:一般用来判断类型、根据返回状态确定资源是否存在、资源是否更新以及更新的时间等 过滤器与拦截器的区别…...

大语言模型初学者指南 (2023)
大语言模型 (LLM) 是深度学习的一个子集,它正在彻底改变自然语言处理领域。它们是功能强大的通用语言模型,可以针对大量数据进行预训练,然后针对特定任务进行微调。这使得LLM能够拥有大量的一般数据。如果一个人想将LLM用于特定目的ÿ…...

日常生活小技巧 -- 单位换算
开发过程中经常需要需要单位换算的地方。 可以使用工具进行转换: 工具:单位转换 常用单位: 1、角度转换 1弧度(rad) 180/PI 度(deg) 57.29577951308232 度(deg) 1度…...

利用深度蛋白质序列嵌入方法通过 Siamese neural network 对 virus-host PPIs 进行精准预测【Patterns,2022】
研究背景: 病毒感染可以导致多种组织特异性损伤,所以 virus-host PPIs 的预测有助于新的治疗方法的研究;目前已有的一些 virus-host PPIs 鉴定或预测方法效果有限(传统实验方法费时费力、计算方法要么基于蛋白结构或基因ÿ…...

opencv 车牌号的定位和识别+UI界面识别系统
目录 一、实现和完整UI视频效果展示 主界面: 识别结果界面:(识别车牌颜色和车牌号) 查看历史记录界面: 二、原理介绍: 车牌检测->图像灰度化->Canny边缘检测->膨胀与腐蚀 边缘检测及预处理…...

如何使用CSS实现一个自适应两栏布局,其中一栏固定宽度,另一栏自适应宽度?
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用Float属性⭐ 使用Flexbox布局⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅!这个专栏是为那些对Web开发感…...