1. 说明
1> linux内核关于task调度这块是比较复杂的,流程也比较长,要从源码一一讲清楚很容易看晕
2> 本篇文章主要是讲清楚cfs公平调度算法如何将task在时钟中断驱动下切换调度,所以与此无关的代码一律略过
3> 本篇只讲最简单的task调度,略过组调度,组调度在下一篇《极简组调度-CGroup如何限制cpu》中讲解
4> 本篇源码来自CentOS7.6的3.10.0-957.el7内核
 
2. 极简task调度核心思想
1> linux采用cfs公平调度算法,其用vruntime记录task运行的cpu时长,每次用重新调度时,总是选择vruntime最小的task进行调度
2> 所有Ready状态的task会分配到不同cpu的rq队列上,等待调度运行
3> 时钟中断中,++当前task运行时间vruntime,并检测当前task运行时间是否超过一个时间片,或者其vruntime比当前cpu rq队列中最小的vruntime task大一个时间片,则设置resched标记(但并不立马进行task切换,因为此时仍在中断上下文中)
4> 所有中断返回后(当然也包括时钟中断),都会jump到ret_from_intr,这里会检查resched标记,如果置位,则调用schedule()选择vruntime最小的task进行调度
 
3. 极简task调度相关数据结构

3.1 名词解释

 
全称
说明
se schedule entity 调度实例,可以是一个task,也可以是一个group(当使用组调度时),linux支持组调度后,将调度实例从原来的task,抽象为se
rq run queue cpu的运行队列,每个cpu一个,处于Ready状态的se挂在对应的cpu运行队列上后,才会被选择投入运行 
cfs_rq cfs rq 公平调度运行队列,因为一般进程都是用cfs调度算法,一般进程的se都是挂在rq.cfs_rq上的
vruntime virtual runtime se的一个重要成员,记录调度实例的cpu运行时长,schedule时,cfs调度每次都选取vruntime最小的se投入运行,这就是cfs调度算法的核心原理

 
 
3.2 数据结构
struct sched_entity
{
unsigned int on_rq; // se是否在rq上,不在的话即使task是Ready状态也不会投入运行的
u64 vruntime; // cpu运行时长,cfs调度算法总是选择该值最小的se投入运行
}; struct task
{
struct sched_entity se; // 调度实例
}; struct rq
{
struct cfs_rq cfs; // 所有要调度的se都挂在cfs rq中
struct task_struct* curr; // 当前cpu上运行的task
}; struct cfs_rq
{
struct rb_root tasks_timeline; // 以vruntime为key,se为value的红黑树根节点,schedule时,cfs调度算法每次从这里挑选vruntime最小的se投入运行
struct rb_node* rb_leftmost; // tasks_timeline红黑树最左的叶子节点,即vruntime最小的se,直接取这个节点以加快速度
sched_entity* curr; // cfs_rq中当前正在运行的se
struct rq* rq; /* cpu runqueue to which this cfs_rq is attached */
unsigned int nr_running; // cfs_rq队列上有多少个se
};
3.3 数据结构关系
 
2.3 极简task调度code
2.3.1 时钟中断
1> task调度的发动机时钟中断触发后,会在smp_apic_timer_interrupt()中处理,经过层层调用,最终会到entity_tick()
entity_tick()
{
update_curr();
// 如果当前cfs_rq上的se大于1,则检查是否要重新调度
if (cfs_rq->nr_running > 1)
check_preempt_tick(cfs_rq, curr);
}

2> update_curr()主要是++当前task se的vruntime(当然这里还对组调度进行了处理,这里不讲组调度,先略过)

void update_curr(struct cfs_rq* cfs_rq)
{
struct sched_entity* curr = cfs_rq->curr;
curr->vruntime += delta_exec; // 增加se的运行时间
}

3> check_preempt_tick()判定当前运行的时间大于sched_slice时,即超过了时间片,或者其vruntime比当前cpu rq队列中最小的vruntime task大一个时间片,就会标记resched,然后等中断返回后会调用schedule()进行task切换

void check_preempt_tick()
{
// 如果运行时间大于sched_slice,则resched
if (delta_exec > ideal_runtime)
resched_task(rq_of(cfs_rq)->curr); // 如果比最小vruntime大一个sched_slice,则resched
se = __pick_first_entity(cfs_rq); // 选择cfs.rb_leftmost的se,即vruntime最小的se
delta = curr->vruntime - se->vruntime;
if (delta > ideal_runtime)
resched_task(rq_of(cfs_rq)->curr);
}

4> resched_curr()非常简单,就是设置一个resched标记位TIF_NEED_RESCHED

void resched_curr(struct rq* rq)
{
struct task_struct* curr = rq->curr;
set_tsk_thread_flag(curr, TIF_NEED_RESCHED);
}
2.3.2 schedule
1> 时钟中断返回后,会jump到ret_from_intr(有兴趣可以去分析这段汇编),如果resched标记被置位,就会调用schedule()进行调度
void schedule()
{
prev = rq->curr;
put_prev_task_fair(rq, prev); // 对当前task进行处理,如果该task属于一个group,还要对组调度进行处理,这里不展开
// 选择下一个task并切换运行
next = pick_next_task(rq); // 选择一个vruntime最小的task进行调度
context_switch(rq, prev, next);
}

2> pick_next_task() → pick_next_task_fair() → pick_next_entity() → __pick_first_entity(),__pick_first_entity()选择vruntime最小的cfs_rq->rb_leftmost节点se进行调度

struct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq)
{
struct rb_node *left = cfs_rq->rb_leftmost;
return rb_entry(left, struct sched_entity, run_node);
}
 

极简cfs公平调度算法的更多相关文章

  1. 基于Linux-3.9.4的mykernel实验环境的极简内核分析

    382 + 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 一.实验环境 win10 -> VMware -> Ubuntu1 ...

  2. 【转帖】极简Docker和Kubernetes发展史

    极简Docker和Kubernetes发展史 https://www.cnblogs.com/chenqionghe/p/11454248.html 2013年 Docker项目开源 2013年,以A ...

  3. CSharpGL(28)得到高精度可定制字形贴图的极简方法

    CSharpGL(28)得到高精度可定制字形贴图的极简方法 回顾 以前我用SharpFont实现了解析TTF文件从而获取字形贴图的功能,并最终实现了用OpenGL渲染文字. 使用SharpFont,美 ...

  4. Vim,极简使用教程,让你瞬间脱离键鼠切换的痛苦

    注:看大家对Vim仇恨极大,其实它只是一种文本操作方式,可以减少键鼠的切换,从而让编辑文本的操作更迅捷.并不等同于IDE,在我看来,它们是两个是包含关系,IDE可以有Vim编辑模式.Vim或许可以通过 ...

  5. CentOS下使用Postfix + Dovecot + Dnsmasq搭建极简局域网邮件系统

    背景 开发环境为局域网,工作内容需要经常查看邮件文件(*.eml),可恶的Foxmail必须验证账户才能进入主界面,才能打开eml文件查看. 无奈搭一个局域网内的邮件系统吧.极简搭建,仅用于通过Fox ...

  6. 在Web应用中接入微信支付的流程之极简清晰版

    在Web应用中接入微信支付的流程之极简清晰版 背景: 在Web应用中接入微信支付,我以为只是调用几个API稍作调试即可. 没想到微信的API和官方文档里隐坑无数,致我抱着怀疑人生的心情悲愤踩遍了丫们布 ...

  7. Snabbt.js – 极简的 JavaScript 动画库

    Snabbt.js 是一个简约的 JavaScript 动画库.它会平移,旋转,缩放,倾斜和调整你的元素.通过矩阵乘法运算,变换等可以任何你想要的方式进行组合.最终的结果通过 CSS3 变换矩阵设置. ...

  8. 在Web应用中接入微信支付的流程之极简清晰版 (转)

    在Web应用中接入微信支付的流程之极简清晰版 背景: 在Web应用中接入微信支付,我以为只是调用几个API稍作调试即可. 没想到微信的API和官方文档里隐坑无数,致我抱着怀疑人生的心情悲愤踩遍了丫们布 ...

  9. 基于 Node.js 平台,快速、开放、极简的 web 开发框架。

    资料地址:http://www.expressjs.com.cn/ Express 基于 Node.js 平台,快速.开放.极简的 web 开发框架. $ npm install express -- ...

  10. HTML5播放器FlowPlayer的极简风格效果

    在线演示 本地下载 使用Flowplayer生成的极简风格的播放器效果.

随机推荐

  1. 面向对象ooDay8

    精华笔记: 接口: 是一种数据类型(引用类型) 由interface定义 只能包含常量和抽象方法(所有数据默认都是常量,所有方法默认都是抽象的) 接口不能被实例化 接口是需要被实现/继承的,实现/派生 ...

  2. Android 自定义SeekBar (二)

    一.前言 本文在 上节 的基础上,讲解自定义拖动条的实现思路. 二.思路 先在res/values文件夹下,自定义控件属性: <?xml version="1.0" enco ...

  3. centos7查看ip地址

    centos7查看ip地址 1.centos7进入终端 安装的centos7虚拟机(无图形界面):输入账号密码进入centos7 2.命令行输入  ip addr 查看 ip地址

  4. 【再学WPF】模板

    1 <!--设置所有的按钮样式--> 2 <Style TargetType="Button"> 3 <Setter Property="M ...

  5. C# DataGridView 新增列 新增行 操作函数 - [ 自律相互分享,共促一起进步 - 社会的正常运维就这么简单,何以权,何以钱...- 张光荣2010年谈社会改正提出的正能量]

    功能: 一.列相关: 1.追加列,左插列,右插列, 2.删除列 二.行相关: 1.追加行,上插行,下插行 2.删除行,删除所有空行,清空所有数据... 原理:根据对鼠标于 DataGridView 点 ...

  6. 微信小程序 入门总结篇

      页面生命周期 Page({ /** * 页面的初始数据 */ data: { }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { }, ...

  7. 在目标服务器Centos7上安装 GitLab Runner

    1.安装提示: 注意:如果你打算通过gitlab-ci,将项目部署到"目标服务器"上,那么这个GitLab Runner就要提前安装到这个"目标服务器"上 (这 ...

  8. 解决df.to_csv 时增加重复双引号的问题

    df.to_csv("test.csv", sep='|',quoting=csv.QUOTE_NONE,index=False,header=True) 转载自 df.to_cs ...

  9. 什么是5G垂直行业?

    什么是垂直行业呢? 感觉"垂直行业"这个词在太多地方遇到,但是这个词的涵盖范围到底是什么呢? 垂直这一概念源于两条直线(或平面)的直角交叉,两条直线是相互作为参照物的.比如,我们可 ...

  10. kubernetes集成GPU原理

    这里以Nvidia GPU设备如何在Kubernetes中管理调度为例研究, 工作流程分为以下两个方面: 如何在容器中使用GPU Kubernetes 如何调度GPU 容器中使用GPU 想要在容器中的 ...