__schedule的一些小细节
(代码主要参考5.10)
1. __schedule的参数preempt
static void __sched notrace __schedule(bool preempt)
preempt
是一个bool的类型的值。
在__schedule
中有这样的一段代码,(有删减):
switch_count = &prev->nivcsw;
prev_state = prev->state;
if (!preempt && prev_state) {
if (signal_pending_state(prev_state, prev)) {
prev_state = TASK_RUNNING;
} else {
......;
deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK);
......;
}
switch_count = &prev->nvcsw;
}
......;
if (likely(prev != next)) {
......;
++switch_context;
}
preempt
代表是否自愿上下文切换。如果是自愿(非抢占进行调度),则为false;如果是非自愿(抢占进行调度),则为true。
struct task_struct
有两个成员nvcsw
和nivcsw
。
nvcsw | nivcsw |
---|---|
Number of Voluntary Context Switches(自愿上下文切换的计数) | Number of InVoluntary Context Switches(非自愿上下文切换计数) |
当一个进程非自愿上下文切换的时候,即被抢占的时候,会少判断一些内容;
而当一个进程自愿上下文切换的时候,即主动放弃CPU的时候,要进行一些判断,会决定prev的状态,是否出队,以及负载均衡的一些操作,这里就不详细描述了。
至于哪些函数,会触发调度__schedule
,它们分别是抢占还是非抢占呢?5.10中如下所示:
function | preempt |
---|---|
do_task_dead | false |
schedule | false |
schedule_idle | false |
preempt_schedule_comm | true |
preempt_schedule_notrace | true |
preempt_schedule_irq | true |
这些函数留给后续分析吧。
2. pick_next_task的两条路径
pick_next_task
函数在__schedule
中调用,挑选下一个要执行的进程。
static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
......;
if (likely(prev->sched_class <= &fair_sched_class &&
rq->nr_running == rq->cfs.h_nr_running)) {---短路径
p = pick_next_task_fair(rq, prev, rf);
......;
}
restart:
......;
for_each_class(class) {---长路径
p = class->pick_next_task(rq);
......;
}
}
是走长路径、还是短路径呢?判断条件为:当前进程的调度类是否为cfs或者idle以及运行队列的进程数量是否与cfs运行队列的进程数量相等。
cfs_rq
中除了h_nr_running
外,还有一个nr_running
,以及rq
中也存在一个nr_running
,它们分别代表什么?
成员 | 解释 |
---|---|
rq的nr_running | 代表运行队列的进程个数 |
cfs_rq的nr_running | 开启组调度的话,代表组调度最上层的group个数 |
cfs_rq的h_nr_running | 代表cfs_rq中的进程个数 |
3. context_switch的四种情况
挑选出下一个要执行的进程next后,要使用context_switch
进行地址空间的切换。
static __always_inline stuct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
{
......;
if (!next->mm) {
enter_lazy_tlb(prev->active);
next->active_mm = prev->active_mm;
if (prev->mm)
mmgrab(prev->active_mm);
else
prev->active_mm = NULL;
} else {
membarrier_switch_mm(rq, prev->active_mm, next->mm);
switch_mm_irqs_off(prev->active_mm, next->mm, next);
if (!prev->mm) {
rq->prev_mm = prev->active_mm;
prev->active_mm = NULL;
}
}
......;
switch_to(prev, next, prev);
barrier();
return finish_task_switch(prev);
}
所谓的四种情况,其实就是prev和next分别是user线程还是kernel线程的组合情况。
prev | next | 操作 |
---|---|---|
kernel | kernel | tlb lazy模式,next借用prev的active_mm,prev的active_mm清空 |
user | kernel | tlb lazy模式,next借用prev的active_mm,prev的mm_count增加计数 |
kernel | user | 地址空间切换,rq记录prev_mm,将prev->active_mm清空 |
user | user | 进程地址空间切换 |
Q1:什么是tlb lazy模式?
tlb是什么?是一个虚拟地址转换成物理地址的快速转换表,常用于cache寻址中。
通常CPU都是进程切换一次,进行一次flush(后面有其他不用全部flush的方法,不详细描述了)。
而内核空间是所有进程通用的,故可以不用flush tlb,这就是tlb lazy模式。
Q2:mm
与active_mm
区别?
mm
的存在与否用于判定该进程是属于user还是kernel;
active_mm
则为实际使用的地址空间,kernel线程总是借用user线程的地址空间。
可以看到,每次kernel线程被切换出去后,它的active_mm
就会被清空,因为是借用的;而每次user线程切换kernel的时候,还会增加一个计数值,用于表示该user线程的地址空间被借用了。
4. switch_to的三个参数
switch_to
的工作主要是切换内核栈,它的具体实现就不在这里分析。
不同的体系架构下也不一样,例如,X86的实现主要使用将当前寄存器的一些值压到prev的内核栈,将内核栈顶指针保存到每个进程相关联的thread_info
,然后切换到next的内核栈,并出栈,将其栈中内容填充到寄存器,以恢复现场。
switch_to
三个参数,其中两个prev的考虑:
设想场景如下:a切换到b,b切换到c,c切换到a。
a压栈时内容:prev为a,next为b;
b压栈时内容:prev为b,next为c;
c压栈时内容:prev为c,next为a。
c切换a,出栈之后呢?
prev为a,next为b
可以看到完全没有c的事了。我们必须得留下c存在过的痕迹。
故这里使用三个参数,其中两个prev,用来留下最新的prev的痕迹。
5. finish_task_switch与context_switch的联动
为什么一定要留下最新的prev的痕迹呢?你有没有想过?
finish_task_switch
的参数就是prev,就是因为它使用到了prev,所以才得留下它。
finsih_task_switch
其实涉及到的东西蛮多,计算vtime,perf追踪点等等。
但就在进程调度过程中,还有一个细节没有处理,还记得是什么吗,参见finish_task_switch
的部分代码:
struct mm_struct *mm = rq->prev_mm;
......;
if (mm) {
membarrier_mm_sync_core_before_usermode(mm);
mmdrop(mm);
}
如果从user到kernel,那么得给借用的mm
增加一个计数;
但是什么时候减去呢?
一旦从kernel到user,rq
就记录下prev使用的active_mm
,在finish_task_switch
中减去这个计数。
__schedule的一些小细节的更多相关文章
- Oracle Sales Cloud:管理沙盒(定制化)小细节2——使用对象触发器更新数字字段
在上一篇 "管理沙盒(定制化)小细节1" 的随笔中,我们使用公式法在 "业务机会" 对象(单头)上建立了 "利润合计" 字段,并将它等于 & ...
- Oracle Sales Cloud:管理沙盒(定制化)小细节1——利用公式创建字段并显示在前端页面
Oracle Sales Cloud(Oracle 销售云)是一套基于Oracle云端的CRM管理系统.由于 Oracle 销售云是基于 Oracle 云环境的,它与传统的管理系统相比,显著特点之一便 ...
- Oracle Sales Cloud:报告和分析(BIEE)小细节2——利用变量和过滤器传参(例如,根据提示展示不同部门的数据)
在上一篇随笔中,我们建立了部门和子部门的双提示,并将部门和子部门做了关联.那么,本篇随笔我们重点介绍利用建好的双提示进行传参. 在操作之前,我们来看一个报告和分析的具体需求: [1] 两个有关联的提示 ...
- Oracle Sales Cloud:报告和分析(BIEE)小细节1——创建双提示并建立关联(例如,部门和子部门提示)
Oracle Sales Cloud(Oracle 销售云)是一套基于Oracle云端的客户商机管理系统,通过提供丰富的功能来帮助提高销售效率,更好地去了解客户,发现和追踪商机,为最终的销售成交 (d ...
- php课程---Json格式规范需要注意的小细节
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式. 易于人阅读和编写.同时也易于机器解析和生成. 它基于JavaScript Programming Lan ...
- ASP.NET MVC 自定义路由中几个需要注意的小细节
本文主要记录在ASP.NET MVC自定义路由时,一个需要注意的参数设置小细节. 举例来说,就是在访问 http://localhost/Home/About/arg1/arg2/arg3 这样的自定 ...
- [小细节,大BUG]记录一些小问题引起的大BUG(长期更新....)
[小细节,大BUG] 6.问题描述:当从Plist文件加载数据,放入到tableView中展示时,有时有数据,有时又没有数据.这是为什么呢?相信很多大牛都想到了:我们一般将加载的数据,转换成模型,放入 ...
- C++在使用Qt中SLOT宏须要注意的一个小细节
大家都知道C++虚函数的机制,对于基类定义为虚函数的地方,子类假设覆写,在基类指针或者引用来指向子类的时候会实现动态绑定. 但假设指针去调用非虚函数,这个时候会调用C++的静态绑定,去推断当前的指针是 ...
- SQL小细节
平时有些小细节,不留意的话很容易得到错误的答案,我们来看下下面的代码,看看你是否能答对呢? ) ,) SELECT @str = '中国CH',@info='MyTest' SELECT [字符串]= ...
随机推荐
- MySQL 基础、安装、配置
1. MySQL 基础 1.1 什么是数据库? 1.2 数据库的类型 1.3 关系型数据库的优点 1.4 MySQL 简介 1.5 MySQL 数据类型 1.6 Mysql 存储引擎 1.7 MySQ ...
- Linux BSP非标准HDMI分辨率
Linux BSP非标准HDMI分辨率 Intrinsyc公司发布了它的一个新的Linux BSP软件的发布 打开-Q820 开发套件基于Linux内核版本.支持的软件功能包括HDMI输出,可以支持标 ...
- Django(56)Mixins工具集的使用
前言 mixins翻译成中文是混入,组件的意思.在DRF中,针对获取列表,检索,创建等操作,都有相应的mixin,一般我们自定义创建的类视图都会继承自GenericAPIView和Mixins一起使用 ...
- 我的N年软件测试感悟
1.前言 大家好!我是Meng前段时间,很荣幸被一合作伙伴邀请发表一篇文章,主题为"这些年,我所从事软件测试的一些感悟",正好趁着这个机会,我也好好总结一下. 2.测试培训 对于软 ...
- C#基础之checked与 unchecked的使用
C#基础之checked与 unchecked的使用 以上都是C#中的两个关键字的使用.据官网给出的相关介绍是:C# 语句既可以在已检查的上下文中执行,也可以在未检查的上下文中执行. 在已检查的上下文 ...
- 简单的Java面向对象程序
上一篇随笔Java静态方法和实例方法的区别以及this的用法,老师看了以后说我还是面向过程的编程,不是面向对象的编程,经过修改以后,整了一个面向对象的出来: /** * 3 延续任务2, 定义表示圆形 ...
- GoLang:通过url将值从view层(.tpl)传递到controller层
beego框架 1.定义路由: beego.Router("/UpdateState/:statename/:id", &controllers.ContentContro ...
- 跟着官方文档学Maven构建生命周期
在IntelliJ IDEA中,显示了Maven的Lifecycle: 只需要学习这些命令,就能构建一个Maven项目. 三个内置生命周期 Maven内置了三个生命周期:clean.default和s ...
- Java并发之ReentrantLock源码解析(二)
在了解如何加锁时候,我们再来了解如何解锁.可重入互斥锁ReentrantLock的解锁方法unlock()并不区分是公平锁还是非公平锁,Sync类并没有实现release(int arg)方法,这里会 ...
- Kubernetes自动横向伸缩集群节点以及介绍PDB资源
在kubernetes中,有HPA在需要的时候创建更多的pod实例.但万一所有的节点都满了,放不下更多pod了,怎么办?显然这个问题并不局限于Autoscaler创建新pod实例的场景.即便是手动创建 ...