Linux CFS调度器之唤醒抢占--Linux进程的管理与调度(三十)
我们也讲解了CFS的很多进程操作
table th:nth-of-type(1){
width: 20%;
}
table th:nth-of-type(2){
width: 20%
;
}
| 信息 | 函数 | 描述 |
|---|---|---|
| 进程入队/出队 | enqueue_task_fair/dequeue_task_fair | 向CFS的就读队列中添加删除进程 |
| 选择最优进程(主调度器) | pick_next_task_fair | 主调度器会按照如下顺序调度 schedule -> __schedule -> 全局pick_next_task 全局的pick_next_task函数会从按照优先级遍历所有调度器类的pick_next_task函数, 去查找最优的那个进程, 当然因为大多数情况下, 系统中全是CFS调度的非实时进程, 因而linux内核也有一些优化的策略 一般情况下选择红黑树中的最左进程left作为最优进程完成调度, 如果选出的进程正好是cfs_rq->skip需要跳过调度的那个进程, 则可能需要再检查红黑树的次左进程second, 同时由于curr进程不在红黑树中, 它可能比较饥渴, 将选择出进程的与curr进程进行择优选取, 同样last进程和next进程由于刚被唤醒, 可能比较饥饿, 优先调度他们能提高系统缓存的命中率 |
| 周期性调度 | task_tick_fair | 周期性调度器的工作由scheduler_tick函数完成, 在scheduler_tick中周期性调度器通过调用curr进程所属调度器类sched_class的task_tick函数完成周期性调度的工作 而entity_tick中则通过check_preempt_tick函数检查是否需要抢占当前进程curr, 如果发现curr进程已经运行了足够长的时间, 其他进程已经开始饥饿, 那么我们就需要通过resched_curr函数来设置重调度标识TIF_NEED_RESCHED, 此标志会提示系统在合适的时间进行调度 |
下面我们到了最后一道工序, 完全公平调度器如何处理一个新创建的进程, 该工作由task_fork_fair函数来完成
1. 处理新进程
我们对完全公平调度器需要考虑的最后一个操作, 创建新进程时的处理函数:task_fork_fair(早期的内核中对应是task_new_fair, 参见LKML-sched: Sanitize fork() handling
1.1 place_entity设置新进程的虚拟运行时间
该函数先用update_curr进行通常的统计量更新, 然后调用此前讨论过的place_entity设置调度实体se的虚拟运行时间
/* 更新统计量 */
update_curr(cfs_rq);
if (curr)
se->vruntime = curr->vruntime;
/* 调整调度实体se的虚拟运行时间 */
place_entity(cfs_rq, se, 1);
我们可以看到, 此时调用place_entity时的initial参数设置为1, 以便用sched_vslice_add计算初始的虚拟运行时间vruntime, 内核以这种方式确定了进程在延迟周期中所占的时间份额, 并转换成虚拟运行时间. 这个是调度器最初向进程欠下的债务.
关于place_entity函数, 我们之前在讲解CFS队列操作的时候已经讲的很详细了
参见linux进程管理与调度之CFS入队出队操作
设想一下子如果休眠进程的vruntime保持不变, 而其他运行进程的 vruntime一直在推进, 那么等到休眠进程终于唤醒的时候, 它的vruntime比别人小很多, 会使它获得长时间抢占CPU的优势, 其他进程就要饿死了. 这显然是另一种形式的不公平,因此CFS是这样做的:在休眠进程被唤醒时重新设置vruntime值,以min_vruntime值为基础,给予一定的补偿,但不能补偿太多. 这个重新设置其虚拟运行时间的工作就是就是通过place_entity来完成的, 另外新进程创建完成后, 也是通过place_entity完成其虚拟运行时间vruntime的设置的.
其中place_entity函数通过第三个参数initial参数来标识新进程创建和进程睡眠后苏醒两种情况的
在进程入队时enqueue_entity设置的initial参数为0, 参见kernel/sched/fair.c, line 3207
在task_fork_fair时设置的initial参数为1, 参见kernel/sched/fair.c, line 8167
1.3 sysctl_sched_child_runs_first控制子进程运行时机
接下来可使用参数sysctl_sched_child_runs_first控制新建子进程是否应该在父进程之前运行. 这通常是有益的, 特别在子进程随后会执行exec系统调用的情况下. 该参数的默认设置是1, 但可以通过/proc/sys/kernel/sched_child_first修改, 代码如下所示
/* 如果设置了sysctl_sched_child_runs_first期望se进程先运行
* 但是se进行的虚拟运行时间却大于当前进程curr
* 此时我们需要保证se的entity_key小于curr, 才能保证se先运行
* 内核此处是通过swap(curr, se)的虚拟运行时间来完成的 */
if (sysctl_sched_child_runs_first && curr && entity_before(curr, se))
{
/*
* Upon rescheduling, sched_class::put_prev_task() will place
* 'current' within the tree based on its new key value.
*/
/* 由于curr的vruntime较小, 为了使se先运行, 交换两者的vruntime */
swap(curr->vruntime, se->vruntime);
/* 设置重调度标识, 通知内核在合适的时间进行进程调度 */
resched_curr(rq);
}
如果entity_before(curr, se), 则父进程curr的虚拟运行时间vruntime小于子进程se的虚拟运行时间, 即在红黑树中父进程curr更靠左(前), 这就意味着父进程将在子进程之前被调度. 这种情况下如果设置了sysctl_sched_child_runs_first标识, 这时候我们必须采取策略保证子进程先运行, 可以通过交换curlr和se的vruntime值, 来保证se进程(子进程)的vruntime小于curr.
1.4 适应迁移的vruntime值
在task_fork_fair函数的最后, 使用了一个小技巧, 通过place_entity计算出的基准虚拟运行时间, 减去了运行队列的min_vruntime.
se->vruntime -= cfs_rq->min_vruntime;
我们前面讲解place_entity的时候说到, 新创建的进程和睡眠后苏醒的进程为了保证他们的vruntime与系统中进程的vruntime差距不会太大, 会使用place_entity来设置其虚拟运行时间vruntime, 在place_entity中重新设置vruntime值,以cfs_rq->min_vruntime值为基础,给予一定的补偿,但不能补偿太多.这样由于休眠进程在唤醒时或者新进程创建完成后会获得vruntime的补偿,所以它在醒来和创建后有能力抢占CPU是大概率事件,这也是CFS调度算法的本意,即保证交互式进程的响应速度,因为交互式进程等待用户输入会频繁休眠
但是这样子也会有一个问题, 我们是以某个cfs就绪队列的min_vruntime值为基础来设定的, 在多CPU的系统上,不同的CPU的负载不一样,有的CPU更忙一些,而每个CPU都有自己的运行队列,每个队列中的进程的vruntime也走得有快有慢,比如我们对比每个运行队列的min_vruntime值,都会有不同, 如果一个进程从min_vruntime更小的CPU (A) 上迁移到min_vruntime更大的CPU (B) 上,可能就会占便宜了,因为CPU (B) 的运行队列中进程的vruntime普遍比较大,迁移过来的进程就会获得更多的CPU时间片。这显然不太公平
同样的问题出现在刚创建的进程上, 还没有投入运行, 没有加入到某个就绪队列中, 它以某个就绪队列的min_vruntime为基准设置了虚拟运行时间, 但是进程不一定在当前CPU上运行, 即新创建的进程应该是可以被迁移的.
CFS是这样做的:
- 当进程从一个CPU的运行队列中出来 (dequeue_entity) 的时候,它的vruntime要减去队列的min_vruntime值
- 而当进程加入另一个CPU的运行队列 ( enqueue_entiry) 时,它的vruntime要加上该队列的min_vruntime值
- 当进程刚刚创建以某个cfs_rq的min_vruntime为基准设置其虚拟运行时间后,也要减去队列的min_vruntime值
这样,进程从一个CPU迁移到另一个CPU之后,vruntime保持相对公平。
参照sched: Remove the cfs_rq dependency
from set_task_cpu()
To prevent boost or penalty in the new cfs_rq caused by delta min_vruntime between the two cfs_rqs, we skip vruntime adjustment.
减去min_vruntime的情况如下
dequeue_entity():
if (!(flags & DEQUEUE_SLEEP))
se->vruntime -= cfs_rq->min_vruntime;
task_fork_fair():
se->vruntime -= cfs_rq->min_vruntime;
switched_from_fair():
if (!se->on_rq && p->state != TASK_RUNNING)
{
/*
* Fix up our vruntime so that the current sleep doesn't
* cause 'unlimited' sleep bonus.
*/
place_entity(cfs_rq, se, 0);
se->vruntime -= cfs_rq->min_vruntime;
}
加上min_vruntime的情形
enqueue_entity:
// http://lxr.free-electrons.com/source/kernel/sched/fair.c?v=4.6#L3196
if (!(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_WAKING))
se->vruntime += cfs_rq->min_vruntime;
attach_task_cfs_rq:
// http://lxr.free-electrons.com/source/kernel/sched/fair.c?v=4.6#L8267
if (!vruntime_normalized(p))
Linux CFS调度器之唤醒抢占--Linux进程的管理与调度(三十)的更多相关文章
- Linux CFS调度器之负荷权重load_weight--Linux进程的管理与调度(二十五)
1. 负荷权重 1.1 负荷权重结构struct load_weight 负荷权重用struct load_weight数据结构来表示, 保存着进程权重值weight.其定义在/include/lin ...
- Linux唤醒抢占----Linux进程的管理与调度(二十三)
1. 唤醒抢占 当在try_to_wake_up/wake_up_process和wake_up_new_task中唤醒进程时, 内核使用全局check_preempt_curr看看是否进程可以抢占当 ...
- Linux用户抢占和内核抢占详解(概念, 实现和触发时机)--Linux进程的管理与调度(二十)
1 非抢占式和可抢占式内核 为了简化问题,我使用嵌入式实时系统uC/OS作为例子 首先要指出的是,uC/OS只有内核态,没有用户态,这和Linux不一样 多任务系统中, 内核负责管理各个任务, 或者说 ...
- Linux核心调度器之周期性调度器scheduler_tick--Linux进程的管理与调度(十八)
我们前面提到linux有两种方法激活调度器:核心调度器和 周期调度器 一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU 另一种是通过周期性的机制, 以固定的频率运行, 不时的检测是否有必要 因 ...
- Linux进程调度器的设计--Linux进程的管理与调度(十七)
1 前景回顾 1.1 进程调度 内存中保存了对每个进程的唯一描述, 并通过若干结构与其他进程连接起来. 调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为 ...
- Linux进程调度器概述--Linux进程的管理与调度(十五)
调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换. 1 背景知识 1.1 什么是调度器 ...
- Linux进程:管理和调度
一:进程管理 进程.轻量级进程和线程 通常定义:进程是程序执行时的一个实例. 这个很像类和实例对象的关系.从内核来看:进程的目的就是担当分配系统资源(CPU,内存等)的实体. 当进程创建时,它几乎和父 ...
- Linux进程上下文切换过程context_switch详解--Linux进程的管理与调度(二十一)
1 前景回顾 1.1 Linux的调度器组成 2个调度器 可以用两种方法来激活调度 一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU 另一种是通过周期性的机制, 以固定的频率运行, 不时的检测 ...
- Linux进程退出详解(do_exit)--Linux进程的管理与调度(十四)
Linux进程的退出 linux下进程退出的方式 正常退出 从main函数返回return 调用exit 调用_exit 异常退出 调用abort 由信号终止 _exit, exit和_Exit的区别 ...
随机推荐
- Win32之隐藏DLL隐藏模块技术
Win32之隐藏DLL隐藏模块技术 这一讲涉及到windows底层技术.跟汇编内容. 我们才可以实现模块隐藏(也称为DLL隐藏) 一丶API反汇编勾引兴趣 我们都用过Windows的进程跟线程API ...
- Go内建函数copy
Go内建函数copy: func copy(dst, src []Type) int 用于将源slice的数据(第二个参数),复制到目标slice(第一个参数). 返回值为拷贝了的数据个数,是len( ...
- Canal 源码走读
前言 canal 是什么? 引用一下官方回答: 阿里巴巴mysql数据库binlog的增量订阅&消费组件 canal 能做什么? 基于日志增量订阅&消费支持的业务: 数据库镜像 数据库 ...
- perl选项、特殊变量、一些函数参考手册
perl一行式程序系列文章:Perl一行式 本文用来收集Perl一行式中涉及到的一些选项.特殊变量的说明,可以用来做速查手册. 本文会逐渐更新. 第一次学Perl一行式时,请直接忽略本文内容,并直接从 ...
- 利用Python测量滴水湖的水面面积
美丽的滴水湖 美丽的滴水湖坐落在上海的东南角,濒临东海,风景秀丽,安静舒适,是旅游.恋爱的绝佳去处.笔者有幸去过一回,对那儿的风土人情留下了深刻的印象,如果有机会,笔者还会多去几次! 滴水湖是 ...
- Spring Boot 设置静态资源访问
问题描述 当使用spring Boot来架设服务系统时,有时候也需要用到前端页面,当然就不可或缺地需要访问其他一些静态资源,比如图片.css.js等文件.那么如何设置Spring Boot网站可以访问 ...
- 依然是关于我空间那篇申请的日志《JavaScript axError:Unexpected token ILLEGAL 很简单的代码……》
接下来要讲的日志现在的标题已经更改为<很简单的代码,但是无法--> 这篇日志地址:http://www.cnblogs.com/herbertchina/p/4475092.html 经过 ...
- linux添加字体
执行命令发现输入命令查看字体列表是提示命令无效: 如上图可以看出,不仅没有中文字体,连字体库都没有,那么接下来就记录一下在Linux CentOS 7中如何安装字体库以及中文字体. 安装字体库 在Ce ...
- CIL中间语言浅谈
CIL中间语言 通用中间语言(Common Intermediate Language,简称CIL)(曾经被称为微软中间语言或MSIL)是一种属于通用语言架构和.NET框架的低阶(lowest-lev ...
- C#程序实现软件开机自动启动的两种常用方法
C#/WPF/WinForm/.NET程序代码实现软件程序开机自动启动的两种常用方法函数的示例与实例带详细注释 方法一:将软件的快捷方式创建到计算机的自动启动目录下(不需要管理员权限) 1.必要引用 ...