mirror of
https://github.com/gatieme/LDD-LinuxDeviceDrivers.git
synced 2026-09-22 20:53:34 +08:00
Linux CFS调度器之pick_next_task_fair选择下一个被调度的进程...
This commit is contained in:
@@ -300,10 +300,16 @@ struct cfs_rq {
|
||||
| skip | 略过进程(不会选择skip指定的进程调度) |
|
||||
|
||||
|
||||
http://www.oenhan.com/task-group-sched
|
||||
http://blog.chinaunix.net/uid-24757773-id-3266304.html
|
||||
http://blog.csdn.net/melong100/article/details/6329201
|
||||
http://blog.csdn.net/wudongxu/article/details/8574737
|
||||
[Linux进程组调度机制分析](http://www.oenhan.com/task-group-sched)
|
||||
|
||||
http://blog.csdn.net/arriod/article/details/7033895
|
||||
[ Linux内核学习笔记(一)CFS完全公平调度类 ](http://blog.chinaunix.net/uid-24757773-id-3266304.html)
|
||||
|
||||
[CFS 调度器学习笔记](http://blog.csdn.net/melong100/article/details/6329201)
|
||||
|
||||
|
||||
[linux调度器(五)——进程管理与CFS](http://blog.csdn.net/wudongxu/article/details/8574737)
|
||||
|
||||
[CFS进程调度](http://blog.csdn.net/arriod/article/details/7033895)
|
||||
|
||||
|
||||
http://www.360doc.com/content/15/1006/18/18252487_503643269.shtml
|
||||
|
||||
@@ -3160,19 +3160,24 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
|
||||
* 总权值显然是增加了,但是所有进程总的运行时期并不一定随之增加
|
||||
* 则每个进程的承诺时间相当于减小了,就是减慢了进程们的虚拟时钟步伐。
|
||||
*/
|
||||
/* initial标识了该进程是新进程 */
|
||||
if (initial && sched_feat(START_DEBIT))
|
||||
vruntime += sched_vslice(cfs_rq, se);
|
||||
|
||||
/* sleeps up to a single latency don't count. */
|
||||
if (!initial) {
|
||||
/* sleeps up to a single latency don't count.
|
||||
* 休眠进程 */
|
||||
if (!initial)
|
||||
{
|
||||
/* 一个调度周期 */
|
||||
unsigned long thresh = sysctl_sched_latency;
|
||||
|
||||
/*
|
||||
* Halve their sleep time's effect, to allow
|
||||
* for a gentler effect of sleepers:
|
||||
*/
|
||||
/* 若设了GENTLE_FAIR_SLEEPERS */
|
||||
if (sched_feat(GENTLE_FAIR_SLEEPERS))
|
||||
thresh >>= 1;
|
||||
thresh >>= 1; /* 补偿减为调度周期的一半 */
|
||||
|
||||
vruntime -= thresh;
|
||||
}
|
||||
|
||||
@@ -274,7 +274,11 @@ enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
|
||||
##2.5 place_entity处理睡眠进程
|
||||
-------
|
||||
|
||||
如果进程此前在睡眠, 那么则调用place_entity处理
|
||||
如果进程此前在睡眠, 那么则调用place_entity处理其虚拟运行时间
|
||||
|
||||
设想一下子如果休眠进程的vruntime保持不变, 而其他运行进程的 vruntime一直在推进, 那么等到休眠进程终于唤醒的时候, 它的vruntime比别人小很多, 会使它获得长时间抢占CPU的优势, 其他进程就要饿死了. 这显然是另一种形式的不公平,因此CFS是这样做的:在休眠进程被唤醒时重新设置vruntime值,以min_vruntime值为基础,给予一定的补偿,但不能补偿太多. 这个重新设置其虚拟运行时间的工作就是就是通过place_entity来完成的, 另外新进程创建完成后, 也是通过place_entity完成其虚拟运行时间vruntime的设置的. place_entity通过其第三个参数initial来标识新进程创建和休眠进程苏醒两种不同情形的.
|
||||
|
||||
|
||||
|
||||
place_entity函数定义在[kernel/sched/fair.c, line 3135](http://lxr.free-electrons.com/source/kernel/sched/fair.c#L3135)中首先会调整进程的虚拟运行时间
|
||||
|
||||
@@ -300,19 +304,24 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
|
||||
* 总权值显然是增加了,但是所有进程总的运行时期并不一定随之增加
|
||||
* 则每个进程的承诺时间相当于减小了,就是减慢了进程们的虚拟时钟步伐。
|
||||
*/
|
||||
/* initial标识了该进程是新进程 */
|
||||
if (initial && sched_feat(START_DEBIT))
|
||||
vruntime += sched_vslice(cfs_rq, se);
|
||||
|
||||
/* sleeps up to a single latency don't count. */
|
||||
if (!initial) {
|
||||
/* sleeps up to a single latency don't count.
|
||||
* 休眠进程 */
|
||||
if (!initial)
|
||||
{
|
||||
/* 一个调度周期 */
|
||||
unsigned long thresh = sysctl_sched_latency;
|
||||
|
||||
/*
|
||||
* Halve their sleep time's effect, to allow
|
||||
* for a gentler effect of sleepers:
|
||||
*/
|
||||
/* 若设了GENTLE_FAIR_SLEEPERS */
|
||||
if (sched_feat(GENTLE_FAIR_SLEEPERS))
|
||||
thresh >>= 1;
|
||||
thresh >>= 1; /* 补偿减为调度周期的一半 */
|
||||
|
||||
vruntime -= thresh;
|
||||
}
|
||||
@@ -324,10 +333,19 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
|
||||
}
|
||||
```
|
||||
|
||||
新进程创建时initial为1,所以它会执行`vruntime += sched_vslice(cfs_rq, se);`这句,而这里的vruntime就是当前CFS就绪队列的min_vruntime,新加进程应该在最近很快被调度,这样减少系统的响应时间,我们已经知道当前进程的vruntime越小,它在红黑树中就会越靠左,就会被很快调度到处理器上执行。但是,Linux内核需要根据新加入的进程的权重决策一下应该何时调度该进程,而不能任意进程都来抢占当前队列中靠左的进程,因为必须保证就绪队列中的所有进程尽量得到他们应得的时间响应, sched_vslice函数就将其负荷权重转换为等价的虚拟时间, 其定义在[kernel/sched/fair.c, line 626](http://lxr.free-electrons.com/source/kernel/sched/fair.c#L626)
|
||||
我们可以看到enqueue_task_fair调用place_entity传递的initial参数为0
|
||||
|
||||
```c
|
||||
place_entity(cfs_rq, se, 0);
|
||||
```
|
||||
|
||||
所以会执行if (!initial)后的语句。因为进程睡眠后,vruntime就不会增加了,当它醒来后不知道过了多长时间,可能vruntime已经比 min_vruntime小了很多,如果只是简单的将其插入到就绪队列中,它将拼命追赶min_vruntime,因为它总是在红黑树的最左面。如果这 样,它将会占用大量的CPU时间,导致红黑树右边的进程被饿死。但是我们又必须及时响应醒来的进程,因为它们可能有一些工作需要立刻处理,所以系统采取了 一种折衷的办法,将当前cfs_rq->min_vruntime时间减去sysctl_sched_latency赋给vruntime,这时它 会被插入到就绪队列的最左边。这样刚唤醒的进程在当前执行进程时间耗尽时就会被调度上处理器执行。当然如果进程没有睡眠那么多时间,我们只需保留原来的时 间vruntime = max_vruntime(se->vruntime, vruntime)。这有什么好处的,我觉得它可以将所有唤醒的进程排个队,睡眠越久的越快得到响应。
|
||||
|
||||
|
||||
函数根据initial的值来区分两种情况, 一般来说只有在新进程被加到系统中时,才会首次设置该参数, 但是这里的情况并非如此:
|
||||
对于新进程创建时initial为1,所以它会执行`vruntime += sched_vslice(cfs_rq, se);`这句,而这里的vruntime就是当前CFS就绪队列的min_vruntime,新加进程应该在最近很快被调度,这样减少系统的响应时间,我们已经知道当前进程的vruntime越小,它在红黑树中就会越靠左,就会被很快调度到处理器上执行。但是,Linux内核需要根据新加入的进程的权重决策一下应该何时调度该进程,而不能任意进程都来抢占当前队列中靠左的进程,因为必须保证就绪队列中的所有进程尽量得到他们应得的时间响应, sched_vslice函数就将其负荷权重转换为等价的虚拟时间, 其定义在[kernel/sched/fair.c, line 626](http://lxr.free-electrons.com/source/kernel/sched/fair.c#L626)
|
||||
|
||||
|
||||
函数就是根据initial的值来区分两种情况, 一般来说只有在新进程被加到系统中时,才会首次设置该参数, 但是这里的情况并非如此:
|
||||
|
||||
由于内核已经承诺在当前的延迟周期内使所有活动进程都至少运行一次, 队列的min_vruntime用作基准虚拟时间, 通过减去sysctl_sched_latency, 则可以确保新唤醒新唤醒的进程只有在当前延迟周期结束后才能运行.
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ fork, vfork和clone的系统调用的入口地址分别是sys_fork, sys_vfork和
|
||||
| 选择最优进程(主调度器) | pick_next_task_fair | 主调度器会按照如下顺序调度 schedule -> __schedule -> 全局pick_next_task<br><br>全局的pick_next_task函数会从按照优先级遍历所有调度器类的pick_next_task函数, 去查找最优的那个进程, 当然因为大多数情况下, 系统中全是CFS调度的非实时进程, 因而linux内核也有一些优化的策略<br><br>一般情况下选择红黑树中的最左进程left作为最优进程完成调度, 如果选出的进程正好是cfs_rq->skip需要跳过调度的那个进程, 则可能需要再检查红黑树的次左进程second, 同时由于curr进程不在红黑树中, 它可能比较饥渴, 将选择出进程的与curr进程进行择优选取, 同样last进程和next进程由于刚被唤醒, 可能比较饥饿, 优先调度他们能提高系统缓存的命中率 |
|
||||
| 周期性调度 | task_tick_fair |周期性调度器的工作由**scheduler_tick函数**完成, 在scheduler_tick中周期性调度器通过调用curr进程所属调度器类sched_class的task_tick函数完成周期性调度的工作<br><br>而entity_tick中则通过**check_preempt_tick**函数检查是否需要抢占当前进程curr, 如果发现curr进程已经运行了足够长的时间, 其他进程已经开始饥饿, 那么我们就需要通过**resched_curr**函数来设置重调度标识TIF_NEED_RESCHED, 此标志会提示系统在合适的时间进行调度 |
|
||||
|
||||
下面我们到了最后一道工序, 完全公平调度器如何处理一个新创建的进程,
|
||||
下面我们到了最后一道工序, 完全公平调度器如何处理一个新创建的进程, 该工作由task_fork_fair函数来完成
|
||||
|
||||
|
||||
|
||||
@@ -65,9 +65,77 @@ fork, vfork和clone的系统调用的入口地址分别是sys_fork, sys_vfork和
|
||||
-------
|
||||
|
||||
|
||||
我们对完全公平调度器需要考虑的最后一个操作, 创建新进程时的处理函数:task_new_fair.
|
||||
我们对完全公平调度器需要考虑的最后一个操作, 创建新进程时的处理函数:task_fork_fair(早期的内核中对应是task_new_fair, 参见[LKML-sched: Sanitize fork() handling](https://lkml.org/lkml/2009/12/9/138)
|
||||
|
||||
该函数的行为可使用参数sysctl_sched_child_runs_first控制. 该参数用于判断新建子进程是否应该在父进程之前运行. 这通常是有益的, 特别在子进程随后会执行exec系统调用的情况下. 该参数的默认设置是1, 但可以通过/proc/sys/kernel/sched_child_first修改
|
||||
##place_entity设置新进程的虚拟运行时间
|
||||
-------
|
||||
|
||||
该函数先用update_curr进行通常的统计量更新, 然后调用此前讨论过的place_entity.
|
||||
该函数先用update_curr进行通常的统计量更新, 然后调用此前讨论过的place_entity设置调度实体se的虚拟运行时间
|
||||
|
||||
|
||||
```c
|
||||
/* 更新统计量 */
|
||||
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](http://lxr.free-electrons.com/source/kernel/sched/fair.c#L3207)
|
||||
>
|
||||
>在task_fork_fair时设置的initial参数为1, 参见[kernel/sched/fair.c, line 8167](http://lxr.free-electrons.com/source/kernel/sched/fair.c#L8167)
|
||||
|
||||
|
||||
##sysctl_sched_child_runs_first控制子进程运行时机
|
||||
-------
|
||||
|
||||
|
||||
接下来可使用参数sysctl_sched_child_runs_first控制新建子进程是否应该在父进程之前运行. 这通常是有益的, 特别在子进程随后会执行exec系统调用的情况下. 该参数的默认设置是1, 但可以通过/proc/sys/kernel/sched_child_first修改, 代码如下所示
|
||||
|
||||
```c
|
||||
/* 如果设置了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.
|
||||
|
||||
|
||||
##
|
||||
|
||||
在task_fork_fair函数的最后, To prevent boost or penalty in the new cfs_rq caused by delta min_vruntime between the two cfs_rqs, we skip vruntime adjustment.
|
||||
|
||||
http://bbs.chinaunix.net/thread-3665947-1-1.html
|
||||
http://ju.outofmemory.cn/entry/105407
|
||||
http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=3665947
|
||||
|
||||
```c
|
||||
se->vruntime -= cfs_rq->min_vruntime;
|
||||
|
||||
raw_spin_unlock_irqrestore(&rq->lock, flags);
|
||||
```
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* called on fork with the child task as argument from the parent's context
|
||||
* - child not yet on the tasklist
|
||||
* - preemption disabled
|
||||
*/
|
||||
static void task_fork_fair(struct task_struct *p)
|
||||
{
|
||||
struct cfs_rq *cfs_rq;
|
||||
struct sched_entity *se = &p->se, *curr;
|
||||
int this_cpu = smp_processor_id();
|
||||
struct rq *rq = this_rq();
|
||||
unsigned long flags;
|
||||
|
||||
raw_spin_lock_irqsave(&rq->lock, flags);
|
||||
|
||||
update_rq_clock(rq);
|
||||
|
||||
cfs_rq = task_cfs_rq(current);
|
||||
curr = cfs_rq->curr;
|
||||
|
||||
/*
|
||||
* Not only the cpu but also the task_group of the parent might have
|
||||
* been changed after parent->se.parent,cfs_rq were copied to
|
||||
* child->se.parent,cfs_rq. So call __set_task_cpu() to make those
|
||||
* of child point to valid ones.
|
||||
*/
|
||||
rcu_read_lock();
|
||||
__set_task_cpu(p, this_cpu);
|
||||
rcu_read_unlock();
|
||||
|
||||
/* 更新统计量 */
|
||||
update_curr(cfs_rq);
|
||||
|
||||
if (curr)
|
||||
se->vruntime = curr->vruntime;
|
||||
/* 调整调度实体se的虚拟运行时间 */
|
||||
place_entity(cfs_rq, se, 1);
|
||||
|
||||
/* 如果设置了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);
|
||||
}
|
||||
|
||||
se->vruntime -= cfs_rq->min_vruntime;
|
||||
|
||||
raw_spin_unlock_irqrestore(&rq->lock, flags);
|
||||
}
|
||||
Reference in New Issue
Block a user