1. ideal_runtime 理想运行时间(根据进程权重在CFS中的比重乘以CFS调度周期,具体函数sched_slice)
2. exec_runtime 实际运行时间
3. vruntime 虚拟运行时间(exec_delta*(NICE_0_weight)/weight)= exec_delta*NICE_0_weight*inv_weight,具体可以看函数calc_delta_fair。
4. prio_to_weight数组
nice前后数字是1.25倍关系,每增加一级,可提升10% cpu使用时间
static const int prio_to_weight[40] = {
/* -20 */ 88761, 71755, 56483, 46273, 36291,
/* -15 */ 29154, 23254, 18705, 14949, 11916,
/* -10 */ 9548, 7620, 6100, 4904, 3906,
/* -5 */ 3121, 2501, 1991, 1586, 1277,
/* 0 */ 1024, 820, 655, 526, 423,
/* 5 */ 335, 272, 215, 172, 137,
/* 10 */ 110, 87, 70, 56, 45,
/* 15 */ 36, 29, 23, 18, 15,
};
虚拟运行时间 vruntime = delta_exec_runtime * (NICE_0_LOAD/nice_n_weight)=delta_exec_runtime * (NICE_0_LOAD*nice_n_inv_weight)>>32 ;//最后把除法变成乘法和移位操作
优先级越高(nice值更小),权重越大,虚拟运行时间增长越慢,时间运行时间会更长.
内核使用优先级0-139,数值越低,优先级越高,其中0-99给实时进程,100-139给普通进程。用户空间nice值(-19,20)对应100-139的优先级.
CFS调度器总是选择虚拟运行时间最小的进程运行
虚拟时间计算函数:
static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
{
if (unlikely(se->load.weight != NICE_0_LOAD))
delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
return delta;
}
5.进程资源
文件,信号量,数据段,堆和栈,寄存器值等等.
6.进程上下文(context)
寄存器和页表(TLB)
7.调度类与调度策略
8. 调度延迟
调度延迟就是让每个可运行状态的进程都运行一次的时间间隔,Linux下的计算函数
sched_nr_latency:值为8,如果CFS中的进程只有8个,那么调度延迟为sysctl_sched_latency(6ms)
如果大于,则调度延迟=nr_running * sysctl_sched_min_granularity(0.75ms),保证每个进程至少运行0.75ms
static u64 __sched_period(unsigned long nr_running)
{
if (unlikely(nr_running > sched_nr_latency))
return nr_running * sysctl_sched_min_granularity;
else
return sysctl_sched_latency;
}
9 就绪队列(runqueue)
每个CPU都有一个percpu结构体变量struct rq,里面包含有结构体struct cfs_rq, cfs_rq维护着可运行状态的调度实体(struct sched_entity)的红黑树,每次调度时,总是选择最左侧的实体(虚拟时间最小)
10. 唤醒抢占粒度(sysctl_sched_wakeup_granularity=1ms)
在wake_up时,抢占当前进程的条件是:
唤醒进程的虚拟时间需要比当前进程小,且差值要大于根据唤醒进程权重来计算的1ms的虚拟时间.
static int
wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)
{
s64 gran, vdiff = curr->vruntime - se->vruntime;/*计算虚拟时间差值 */
if (vdiff <= 0)
return -1;
gran = wakeup_gran(se);/*1ms对应的虚拟时间 */
if (vdiff > gran)
return 1;/*如果大于1ms对应的虚拟时间 */
return 0;
}
11.周期性抢占调度(scheduler_tick)
抢占的条件:由函数check_preempt_tick判断
1. 实际运行时间大于理想运行时间
2.两个进程的虚拟时间差值大于理想运行时间
不发生抢占的条件:函数
1.当前进程运行时间小于最小粒度(sysctl_sched_min_granularity(0.75ms))
2. 当前进程虚拟时间更小
static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
ideal_runtime = sched_slice(cfs_rq, curr);/*根据进程权重分到的理想运行时间 */
delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
if (delta_exec > ideal_runtime) {/*运行时间大于理想时间 */
resched_curr(rq_of(cfs_rq));
return;
}
/*如果运行时间小于最小粒度(0.75ms),则不抢占 */
if (delta_exec < sysctl_sched_min_granularity)
return;
/*获取CFS中最小虚拟时间进程 */
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));函数
}