Linux CFS调度器之task_tick_fair处理周期性调度器--Linux进程的管理与调度(二十九)
1. CFS如何处理周期性调度器
周期性调度器的工作由scheduler_tick函数完成(定义在kernel/sched/core.c, line 2910), 在scheduler_tick中周期性调度器通过调用curr进程所属调度器类sched_class的task_tick函数完成周期性调度的工作
周期调度的工作形式上sched_class调度器类的task_tick函数完成, CFS则对应task_tick_fair函数, 但实际上工作交给entity_tick完成.
2 CFS的周期性调度
2.1 task_tick_fair与周期性调度
CFS完全公平调度器类通过task_tick_fair函数完成周期性调度的工作, 该函数定义在kernel/sched/fair.c?v=4.6#L8119
/*
* scheduler tick hitting a task of our scheduling class:
*/
static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
{
struct cfs_rq *cfs_rq;
/* 获取到当前进程curr所在的调度实体 */
struct sched_entity *se = &curr->se;
/* for_each_sched_entity
* 在不支持组调度条件下, 只循环一次
* 在组调度的条件下, 调度实体存在层次关系,
* 更新子调度实体的同时必须更新父调度实体 */
for_each_sched_entity(se)
{
/* 获取当当前运行的进程所在的CFS就绪队列 */
cfs_rq = cfs_rq_of(se);
/* 完成周期性调度 */
entity_tick(cfs_rq, se, queued);
}
if (static_branch_unlikely(&sched_numa_balancing))
task_tick_numa(rq, curr);
}
我们可以看到, CFS周期性调度的功能实际上是委托给entity_tick函数来完成的
2.2 entity_tick函数
在task_tick_fair中, 内核将CFS周期性调度的实际工作交给了entity_tick来完成, 该函数定义在kernel/sched/fair.c, line 3470中, 如下所示
static void
entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
{
/*
* Update run-time statistics of the 'current'.
*/
update_curr(cfs_rq);
/*
* Ensure that runnable average is periodically updated.
*/
update_load_avg(curr, 1);
update_cfs_shares(cfs_rq);
#ifdef CONFIG_SCHED_HRTICK
/*
* queued ticks are scheduled to match the slice, so don't bother
* validating it and just reschedule.
*/
if (queued) {
resched_curr(rq_of(cfs_rq));
return;
}
/*
* don't let the period tick interfere with the hrtick preemption
*/
if (!sched_feat(DOUBLE_TICK) &&
hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))
return;
#endif
if (cfs_rq->nr_running > 1)
check_preempt_tick(cfs_rq, curr);
}
首先, 一如既往的使用update_curr来更新统计量
接下来是hrtimer的更新, 这些由内核通过参数CONFIG_SCHED_HRTICK开启
然后如果cfs就绪队列中进程数目nr_running少于两个(< 2)则实际上无事可做. 因为如果某个进程应该被抢占, 那么至少需要有另一个进程能够抢占它(即cfs_rq->nr_running > 1)
如果进程的数目不少于两个, 则由check_preempt_tick作出决策
if (cfs_rq->nr_running > 1)
check_preempt_tick(cfs_rq, curr);
2.3 check_preempt_tick函数
在entity_tick中, 如果cfs的就绪队列中进程数目不少于2, 说明至少需要有另外一个进程能够抢占当前进程, 此时内核交给check_preempt_tick作出决策. check_preempt_tick函数定义在kernel/sched/fair.c, line 3308
/*
* Preempt the current task with a newly woken task if needed:
*/
static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
unsigned long ideal_runtime, delta_exec;
struct sched_entity *se;
s64 delta;
/* 计算curr的理论上应该运行的时间 */
ideal_runtime = sched_slice(cfs_rq, curr);
/* 计算curr的实际运行时间
* sum_exec_runtime: 进程执行的总时间
* prev_sum_exec_runtime:进程在切换进CPU时的sum_exec_runtime值 */
delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
/* 如果实际运行时间比理论上应该运行的时间长
* 说明curr进程已经运行了足够长的时间
* 应该调度新的进程抢占CPU了 */
if (delta_exec > ideal_runtime)
{
resched_curr(rq_of(cfs_rq));
/*
* The current task ran long enough, ensure it doesn't get
* re-elected due to buddy favours.
*/
clear_buddies(cfs_rq, curr);
return;
}
/*
* Ensure that a task that missed wakeup preemption by a
* narrow margin doesn't have to wait for a full slice.
* This also mitigates buddy induced latencies under load.
*/
if (delta_exec < sysctl_sched_min_granularity)
return;
se = __pick_first_entity(cfs_rq);
delta = curr->vruntime - se->vruntime;
if (delta < 0)
return;
if (delta > ideal_runtime)
resched_curr(rq_of(cfs_rq));
}
check_preempt_tick函数的目的在于, 判断是否需要抢占当前进程. 确保没有哪个进程能够比延迟周期中确定的份额运行得更长. 该份额对应的实际时间长度在sched_slice中计算.
而上一节我们提到, 进程在CPU上已经运行的实际时间间隔由sum_exec_runtime - prev_sum_runtime给出.
还记得上一节, 在set_next_entity函数的最后, 将选择出的调度实体se的sum_exec_runtime保存在了prev_sum_exec_runtime中, 因为该调度实体指向的进程, 马上将抢占处理器成为当前活动进程, 在CPU上花费的实际时间将记入sum_exec_runtime, 因此内核会在prev_sum_exec_runtime保存此前的设置. 要注意进程中的sum_exec_runtime没有重置. 因此差值sum_exec_runtime - prev_sum_runtime确实标识了在CPU上执行花费的实际时间.
在处理周期性调度时, 这个差值就显得格外重要
因此抢占决策很容易做出决定, 如果检查发现当前进程运行需要被抢占, 那么通过resched_task发出重调度请求. 这会在task_struct中设置TIF_NEED_RESCHED标志, 核心调度器会在下一个适当的时机发起重调度.
其实需要抢占的条件有下面两种可能性
- curr进程的实际运行时间delta_exec比期望的时间间隔ideal_runtime长
此时说明curr进程已经运行了足够长的时间
- curr进程与红黑树中最左进程left虚拟运行时间的差值大于curr的期望运行时间ideal_runtime
此时说明红黑树中最左结点left与curr节点更渴望处理器, 已经接近于饥饿状态, 这个我们可以这样理解, 相对于curr进程来说, left进程如果参与调度, 其期望运行时间应该域curr进程的期望时间ideal_runtime相差不大, 而此时如果curr->vruntime - se->vruntime > curr.ideal_runtime, 我们可以初略的理解为curr进程已经优先于left进程多运行了一个周期, 而left又是红黑树总最饥渴的那个进程, 因此curr进程已经远远领先于队列中的其他进程, 此时应该补偿其他进程。
如果检查需要发生抢占, 则内核通过resched_curr(rq_of(cfs_rq))设置重调度标识, 从而触发延迟调度
2.4 resched_curr设置重调度标识TIF_NEED_RESCHED
周期性调度器并不显式进行调度, 而是采用了延迟调度的策略, 如果发现需要抢占, 周期性调度器就设置进程的重调度标识TIF_NEED_RESCHED, 然后由主调度器完成调度工作.
TIF_NEED_RESCHED标识, 表明进程需要被调度, TIF前缀表明这是一个存储在进程thread_info中flag字段的一个标识信息
在内核的一些关键位置, 会检查当前进程是否设置了重调度标志TLF_NEDD_RESCHED, 如果该进程被其他进程设置了TIF_NEED_RESCHED标志, 则函数重新执行进行调度
前面我们在check_preempt_tick中如果发现curr进程已经运行了足够长的时间, 其他进程已经开始饥饿, 那么我们就需要通过resched_curr来设置重调度标识TIF_NEED_RESCHED
resched_curr函数定义在kernel/sched/core.c, line 446中, 并没有什么复杂的工作, 其实就是通过set_tsk_need_resched(curr);函数设置重调度标识
3 总结
周期性调度器的工作由scheduler_tick函数完成(定义在kernel/sched/core.c, line 2910), 在scheduler_tick中周期性调度器通过调用curr进程所属调度器类sched_class的task_tick函数完成周期性调度的工作
周期调度的工作形式上sched_class调度器类的task_tick函数完成, CFS则对应task_tick_fair函数, 但实际上工作交给entity_tick完成.
而entity_tick中则通过check_preempt_tick函数检查是否需要抢占当前进程curr, 如果发现curr进程已经运行了足够长的时间, 其他进程已经开始饥饿, 那么我们就需要通过resched_curr函数来设置重调度标识TIF_NEED_RESCHED
其中check_preempt_tick检查可抢占的条件如下
curr进程的实际运行时间delta_exec比期望的时间间隔ideal_runtime长, 此时说明curr进程已经运行了足够长的时间
curr进程与红黑树中最左进程left虚拟运行时间的差值大于curr的期望运行时间ideal_runtime, 此时我们可以理解为curr进程已经优先于left进程多运行了一个周期, 而left又是红黑树总最饥渴的那个进程, 因此curr进程已经远远领先于队列中的其他进程, 此时应该补偿其他进程
Linux CFS调度器之task_tick_fair处理周期性调度器--Linux进程的管理与调度(二十九)的更多相关文章
- Linux核心调度器之周期性调度器scheduler_tick--Linux进程的管理与调度(十八)
我们前面提到linux有两种方法激活调度器:核心调度器和 周期调度器 一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU 另一种是通过周期性的机制, 以固定的频率运行, 不时的检测是否有必要 因 ...
- Linux进程核心调度器之主调度器schedule--Linux进程的管理与调度(十九)
主调度器 在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule, 从系统调用返回后, 内核也会检查当前进程是否设置了重调度标志TLF_N ...
- Linux CFS调度器之负荷权重load_weight--Linux进程的管理与调度(二十五)
1. 负荷权重 1.1 负荷权重结构struct load_weight 负荷权重用struct load_weight数据结构来表示, 保存着进程权重值weight.其定义在/include/lin ...
- Linux进程上下文切换过程context_switch详解--Linux进程的管理与调度(二十一)
1 前景回顾 1.1 Linux的调度器组成 2个调度器 可以用两种方法来激活调度 一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU 另一种是通过周期性的机制, 以固定的频率运行, 不时的检测 ...
- Linux唤醒抢占----Linux进程的管理与调度(二十三)
1. 唤醒抢占 当在try_to_wake_up/wake_up_process和wake_up_new_task中唤醒进程时, 内核使用全局check_preempt_curr看看是否进程可以抢占当 ...
- Linux进程退出详解(do_exit)--Linux进程的管理与调度(十四)
Linux进程的退出 linux下进程退出的方式 正常退出 从main函数返回return 调用exit 调用_exit 异常退出 调用abort 由信号终止 _exit, exit和_Exit的区别 ...
- Linux CFS调度器之唤醒抢占--Linux进程的管理与调度(三十)
我们也讲解了CFS的很多进程操作 table th:nth-of-type(1){ width: 20%; } table th:nth-of-type(2){ width: 20% ; } 信息 函 ...
- Linux CFS调度器之虚拟时钟vruntime与调度延迟--Linux进程的管理与调度(二十六)
1 虚拟运行时间(今日内容提醒) 1.1 虚拟运行时间的引入 CFS为了实现公平,必须惩罚当前正在运行的进程,以使那些正在等待的进程下次被调度. 具体实现时,CFS通过每个进程的虚拟运行时间(vrun ...
- Linux CFS调度器之pick_next_task_fair选择下一个被调度的进程--Linux进程的管理与调度(二十八)
1. CFS如何选择最合适的进程 每个调度器类sched_class都必须提供一个pick_next_task函数用以在就绪队列中选择一个最优的进程来等待调度, 而我们的CFS调度器类中, 选择下一个 ...
随机推荐
- (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)
在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机 前提:服务器端安装桌面版的centos系统 CentOS Linux release 7.5.1804 (Core) ...
- 利用redis实现分布式锁
分布式锁一般有三种实现方式: 1. 数据库乐观锁: 2. 基于ZooKeeper的分布式锁: 3. 基于Redis的分布式锁: 这里大概说一下三种方式的优缺点,数据库乐观锁优点是实现简单,只需要for ...
- Linux核心命令
Linux核心命令 strace(查看系统调用的一个过程) 例:strace cat /test.txt netstat perf top pidstat mpstat dstat vmstat sl ...
- [转]RPA流程自动化-Blueprism认证考试介绍
本文转自:https://www.cnblogs.com/digod/p/9190186.html RPA流程自动化-Blueprism认证考试介绍 接触RPA有一段时间了,几种RPA相关工具也都试用 ...
- Cookie 用法
//Cookie:是一个客户端状态保持机制,(网站的数据是存在客户端),与隐藏域与ViewState对象都属于这种客户端状态保持,Cookie中存储的是关于网站相关的文本字符串数据.Cookie的存储 ...
- 用kafka实现消息推送
一个人知道的Topic是单点推送,大家都知道Topic是广播. kafka消息消费机制: 1.广播消费:通过定义topic前缀来标识属于广播的消息(例如:topicname:gonggao153568 ...
- mysql启动事件
1.查看时间是否开启 SHOW VARIABLES LIKE 'event_scheduler' 若是查看结果为OFF,的话开启ON 2.设置事件开启 SET GLOBAL event_schedul ...
- 10个用Console来Debug的高级技巧
译者按: 我们往往会局限在自己熟悉的知识圈,但也应担偶尔拓展一下,使用一些不常见而又有用的技巧,扩大自己的舒适圈. 原文: 10 Tips for Javascript Debugging Like ...
- jQuery 事件 - ready() 方法
转载:http://www.w3school.com.cn/jquery/jquery_hide_show.asp 实例 在文档加载后激活函数: $(document).ready(function( ...
- jfinal中excel表格导出
今天工作中遇到了excel表格导出的操作,还好有写好的模板,不然我也是焦头烂额,下面记录一下excel表格导出的操作步骤,同时用来给正在学习jfinal的小伙伴一些参考和学习. 首先我们需要把表格查询 ...