CFS调度器之虚拟时钟...

This commit is contained in:
gatieme
2016-06-29 23:40:00 +08:00
parent 246ba7316b
commit bcbf891d0c
9 changed files with 6022 additions and 5747 deletions
@@ -1,4 +1,4 @@
Linux进程调度器的设计
Linux进程调度CFS调度
=======
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,4 @@
Linux进程调度器的设计
Linux CFS调度器之虚拟时钟与调度延迟
=======
@@ -0,0 +1,162 @@
Linux CFS调度器之队列操作
=======
| 日期 | 内核版本 | 架构| 作者 | GitHub| CSDN |
| ------- |:-------:|:-------:|:-------:|:-------:|:-------:|
| 2016-06-29 | [Linux-4.6](http://lxr.free-electrons.com/source/?v=4.6) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux进程管理与调度](http://blog.csdn.net/gatieme/article/details/51456569) |
CFS负责处理普通非实时进程, 这类进程是我们linux中最普遍的进程
#前景回顾
-------
## CFS调度算法
-------
**CFS调度算法的思想**
理想状态下每个进程都能获得相同的时间片,并且同时运行在CPU上,但实际上一个CPU同一时刻运行的进程只能有一个。也就是说,当一个进程占用CPU时,其他进程就必须等待。CFS为了实现公平,必须惩罚当前正在运行的进程,以使那些正在等待的进程下次被调度.
## 负荷权重和虚拟时钟
**虚拟时钟是红黑树排序的依据**
具体实现时,CFS通过每个进程的**虚拟运行时间(vruntime)**来衡量哪个进程最值得被调度. CFS中的就绪队列是一棵以vruntime为键值的红黑树,虚拟时间越小的进程越靠近整个红黑树的最左端。因此,调度器每次选择位于红黑树最左端的那个进程,该进程的vruntime最小.
**优先级计算负荷权重, 负荷权重和当前时间计算出虚拟运行时间**
虚拟运行时间是通过进程的实际运行时间和进程的权重(weight)计算出来的。在CFS调度器中,将进程优先级这个概念弱化,而是强调进程的权重。一个进程的权重越大,则说明这个进程更需要运行,因此它的虚拟运行时间就越小,这样被调度的机会就越大。而,CFS调度器中的权重在内核是对用户态进程的优先级nice值, 通过prio_to_weight数组进行nice值和权重的转换而计算出来的
**虚拟时钟相关公式**
linux内核采用了计算公式:
| 属性 | 公式 | 描述 |
|:-------:|:-------:|
| ideal_time | sum_runtime *se.weight/cfs_rq.weight | 每个进程应该运行的时间 |
| sum_exec_runtime | | 运行队列中所有任务运行完一遍的时间 |
| se.weight | | 当前进程的权重 |
| cfs.weight | | 整个cfs_rq的总权重 |
这里se.weight和cfs.weight根据上面讲解我们可以算出, sum_runtime是怎们计算的呢,linux内核中这是个经验值,其经验公式是
| 条件 | 公式 |
|:-------:|:-------:|
| 进程数 > sched_nr_latency | sum_runtime=sysctl_sched_min_granularity *nr_running |
| 进程数 <=sched_nr_latency | sum_runtime=sysctl_sched_latency = 20ms |
>注:sysctl_sched_min_granularity =4ms
>
>sched_nr_latency是内核在一个延迟周期中处理的最大活动进程数目
linux内核代码中是通过一个叫vruntime的变量来实现上面的原理的,即:
每一个进程拥有一个vruntime,每次需要调度的时候就选运行队列中拥有最小vruntime的那个进程来运行,vruntime在时钟中断里面被维护,每次时钟中断都要更新当前进程的vruntime,即vruntime以如下公式逐渐增长:
| 条件 | 公式 |
|:-------:|:-------:|
| curr.nice!=NICE_0_LOAD | vruntime += delta* NICE_0_LOAD/se.weight; |
| curr.nice=NICE_0_LOAD | vruntime += delta; |
##今日内容--CFS进程入队和出队
-------
完全公平调度器CFS中有两个函数可用来增删队列的成员:enqueue_task_fair和dequeue_task_fair分别用来向CFS就绪队列中添加或者删除进程
# enqueue_task_fair入队操作
-------
## enque_task_fair函数
-------
向就绪队列中放置新进程的工作由函数enqueue_task_fair函数完成, 该函数定义在[kernel/sched/fair.c, line 5442](http://lxr.free-electrons.com/source/kernel/sched/fair.c?v=4.6#L5442), 其函数原型如下
该函数将task_struct *p所指向的进程插入到rq所在的就绪队列中, 除了指向所述的就绪队列rq和task_struct的指针外, 该函数还有另外一个参数wakeup. 这使得可以指定入队的进程是否最近才被唤醒并转换为运行状态(此时需指定wakeup = 1), 还是此前就是可运行的(那么wakeup = 0).
```c
static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
```
enqueue_task_fair的执行流程如下
* 如果通过struct sched_entity的on_rq成员判断进程已经在就绪队列上, 则无事可做.
* 否则, 具体的工作委托给enqueue_entity完成, 其中内核会借机用update_curr更新统计量.
*
## for_each_sched_entity
-------
首先内核查找到待天机进程p所在的调度实体信息, 然后通过for_each_sched_entity循环所有调度实体,
```c
// enqueue_task_fair函数
struct cfs_rq *cfs_rq;
struct sched_entity *se = &p->se;
for_each_sched_entity(se)
{
/* ...... */
}
````
, p所在的调度时提就一个为嘛要循环才能遍历啊, ., , , ,
linux对组调度的支持可以通过CONFIG_FAIR_GROUP_SCHED来启用, , , for_each_sched_entity函数尤为明显, [CONFIG_FAIR_GROUP_SCHED](http://lxr.free-electrons.com/source/kernel/sched/fair.c?v=4.6#L246)和[不启用CONFIG_FAIR_GROUP_SCHED](http://lxr.free-electrons.com/source/kernel/sched/fair.c?v=4.6#L367)
```c
#ifdef CONFIG_FAIR_GROUP_SCHED
/* An entity is a task if it doesn't "own" a runqueue */
#define entity_is_task(se) (!se->my_q)
// http://lxr.free-electrons.com/source/kernel/sched/fair.c?v=4.6#L266
/* Walk up scheduling entities hierarchy */
#define for_each_sched_entity(se) \
for (; se; se = se->parent)
#else /* !CONFIG_FAIR_GROUP_SCHED */
#define entity_is_task(se) 1
// http://lxr.free-electrons.com/source/kernel/sched/fair.c?v=4.6#L381
#define for_each_sched_entity(se) \
for (; se; se = NULL)
```
* 如果通过struct sched_entity的on_rq成员判断进程已经在就绪队列上, 则无事可做.
* 否则, 具体的工作委托给enqueue_entity完成, 其中内核会借机用update_curr更新统计量.
```c
// enqueue_task_fair函数
/* 如果当前进程已经在就绪队列上 */
if (se->on_rq)
break;
/* 获取到当前进程所在的cfs_rq就绪队列 */
cfs_rq = cfs_rq_of(se);
/* 内核委托enqueue_entity完成真正的插入工作 */
enqueue_entity(cfs_rq, se, flags);
````
##enqueue_entity插入进程
-------
首先如果进程最近正在运行, 其虚拟时间时间仍然有效, 那么(除非它当前在执行中)他可以
```c
````
@@ -0,0 +1,57 @@
/*
* The dequeue_task method is called before nr_running is
* decreased. We remove the task from the rbtree and
* update the fair scheduling stats:
*/
static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
struct cfs_rq *cfs_rq;
struct sched_entity *se = &p->se;
int task_sleep = flags & DEQUEUE_SLEEP;
for_each_sched_entity(se) {
cfs_rq = cfs_rq_of(se);
dequeue_entity(cfs_rq, se, flags);
/*
* end evaluation on encountering a throttled cfs_rq
*
* note: in the case of encountering a throttled cfs_rq we will
* post the final h_nr_running decrement below.
*/
if (cfs_rq_throttled(cfs_rq))
break;
cfs_rq->h_nr_running--;
/* Don't dequeue parent if it has other entities besides us */
if (cfs_rq->load.weight) {
/*
* Bias pick_next to pick a task from this cfs_rq, as
* p is sleeping when it is within its sched_slice.
*/
if (task_sleep && parent_entity(se))
set_next_buddy(parent_entity(se));
/* avoid re-evaluating load for this entity */
se = parent_entity(se);
break;
}
flags |= DEQUEUE_SLEEP;
}
for_each_sched_entity(se) {
cfs_rq = cfs_rq_of(se);
cfs_rq->h_nr_running--;
if (cfs_rq_throttled(cfs_rq))
break;
update_load_avg(se, 1);
update_cfs_shares(cfs_rq);
}
if (!se)
sub_nr_running(rq, 1);
hrtick_update(rq);
}
@@ -0,0 +1,51 @@
/*
* The enqueue_task method is called before nr_running is
* increased. Here we update the fair scheduling stats and
* then put the task into the rbtree:
*/
static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
struct cfs_rq *cfs_rq;
struct sched_entity *se = &p->se;
for_each_sched_entity(se)
{
/* 如果当前进程已经在就绪队列上 */
if (se->on_rq)
break;
/* 获取到当前进程所在的cfs_rq就绪队列 */
cfs_rq = cfs_rq_of(se);
/* 内核委托enqueue_entity完成真正的插入工作 */
enqueue_entity(cfs_rq, se, flags);
/*
* end evaluation on encountering a throttled cfs_rq
*
* note: in the case of encountering a throttled cfs_rq we will
* post the final h_nr_running increment below.
*/
if (cfs_rq_throttled(cfs_rq))
break;
cfs_rq->h_nr_running++;
flags = ENQUEUE_WAKEUP;
}
for_each_sched_entity(se) {
cfs_rq = cfs_rq_of(se);
cfs_rq->h_nr_running++;
if (cfs_rq_throttled(cfs_rq))
break;
update_load_avg(se, 1);
update_cfs_shares(cfs_rq);
}
if (!se)
add_nr_running(rq, 1);
hrtick_update(rq);
}