一、目的和背景
这种实现的方法,就是在一个理想的时间段内,所有的进程都应该获得一次调度的执行机会(但是执行的时间可能要根据优先级不同,由于非实时任务的优先级static_priority只能通过nice修改,所以nice是修改非实时任务优先级的一个基本方法)。这样的好处是“不患寡而患不均”,即使CPU的切换很频繁,但是优点是所有的线程都能被调度到,这样大部分人都说他好了。从另一个方面讲,现在系统中已经开始支持实时任务了,所以既然设置为默认的非实时任务,那么如果真的十分care这个运行时间,那么可以修改为实时任务。
二、实现方法
每个任务中增加了一个sched_entity,
struct sched_entity se;这个是给非实时的一个CFS结构是用的一个调度结构。
struct sched_rt_entity rt;我想这个应该是给RR类型的实时任务是用的一个结构。
这个结构直接嵌入在task_struct中,
sched.c
/*
* Nice levels are multiplicative, with a gentle 10% change for every
* nice level changed. I.e. when a CPU-bound task goes from nice 0 to
* nice 1, it will get ~10% less CPU time than another CPU-bound task
* that remained on nice 0.
*
* The "10% effect" is relative and cumulative: from _any_ nice level,
* if you go up 1 level, it's -10% CPU usage, if you go down 1 level
* it's +10% CPU usage. (to achieve that we use a multiplier of 1.25.
* If a task goes up by ~10% and another task goes down by ~10% then
* the relative distance between them is ~25%.)
*/
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,
};
static void set_load_weight(struct task_struct *p)
{
/*
* SCHED_IDLE tasks get minimal weight:
*/
if (p->policy == SCHED_IDLE) {
p->se.load.weight = WEIGHT_IDLEPRIO;
p->se.load.inv_weight = WMULT_IDLEPRIO;
return;
}
p->se.load.weight = prio_to_weight[p->static_prio - MAX_RT_PRIO];
p->se.load.inv_weight = prio_to_wmult[p->static_prio - MAX_RT_PRIO];
}
三、控制结构
为了支持这样的一个策略,那么我们就应该能够快速的计算出并找到系统中下次应该运行的任务。由于原来的实时任务是通过定长链表的O(1)调度,这里我们就换个花样,是用红黑树。这样当我们来寻找下一个CFS任务的时候,只要找到这个红黑树的最左最下的节点就可以了,而这个红黑树的根节点为每个CPU定义一个,从而可以快速的找到。检索比较方便,另一个比较方便的就是查找维护结构平衡的问题,这个一般式hi通过调度中的put_prev_task接口来实现。
struct rb_node run_node;
四、weight对调度时间的影响
/*
* We calculate the wall-time slice from the period by taking a part
* proportional to the weight.
*
* s = p*P[w/rw]
*/
static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq);
for_each_sched_entity(se) {
struct load_weight *load;
struct load_weight lw;
cfs_rq = cfs_rq_of(se);
load = &cfs_rq->load;
if (unlikely(!se->on_rq)) {
lw = cfs_rq->load;
update_load_add(&lw, se->load.weight);
load = &lw;
}
slice = calc_delta_mine(slice, se->load.weight, load);
}
return slice;
}
/*
* delta *= weight / lw
*/
static unsigned long
calc_delta_mine(unsigned long delta_exec, unsigned long weight,
struct load_weight *lw)
{
u64 tmp;
if (!lw->inv_weight) {
if (BITS_PER_LONG > 32 && unlikely(lw->weight >= WMULT_CONST))
lw->inv_weight = 1;
else
lw->inv_weight = 1 + (WMULT_CONST-lw->weight/2)
/ (lw->weight+1);
}
tmp = (u64)delta_exec * weight;
/*
* Check whether we'd overflow the 64-bit multiplication:
*/
if (unlikely(tmp > WMULT_CONST))
tmp = SRR(SRR(tmp, WMULT_SHIFT/2) * lw->inv_weight,
WMULT_SHIFT/2);
else
tmp = SRR(tmp * lw->inv_weight, WMULT_SHIFT);
return (unsigned long)min(tmp, (u64)(unsigned long)LONG_MAX);
}
对于一个slice的计算
/*
* The idea is to set a period in which each task runs once.
*
* When there are too many tasks (sysctl_sched_nr_latency) we have to stretch
* this period because otherwise the slices get too small.
*
* p = (nr <= nl) ? l : l*nr/nl
*/
static u64 __sched_period(unsigned long nr_running)
{
u64 period = sysctl_sched_latency;
unsigned long nr_latency = sched_nr_latency;
if (unlikely(nr_running > nr_latency)) {
period = sysctl_sched_min_granularity;
period *= nr_running;
}
return period;
}
可以看到,这个slice是按照一个时间的延迟量和当前的系统中的可运行任务的总量计算出的一个时间片,通过这个时间片为基本单位,然后计算出系统中的特定任务将会执行的时间片的数量。
但是这个时间片的长度和传统的时间片长度并不一样,作者的相关注释为
/*
* Targeted preemption latency for CPU-bound tasks:
* (default: 6ms * (1 + ilog(ncpus)), units: nanoseconds)
*
* NOTE: this latency value is not the same as the concept of
* 'timeslice length' - timeslices in CFS are of variable length
* and have no persistent notion like in traditional, time-slice
* based scheduling concepts.
*
* (to see the precise effective timeslice length of your workload,
* run vmstat and monitor the context-switches (cs) field)
*/
unsigned int sysctl_sched_latency = 6000000ULL; 这个值表示系统中在这个时间段之内一个线程最好能够得到一次调度。
unsigned int normalized_sysctl_sched_latency = 6000000ULL;
/*
* is kept at sysctl_sched_latency / sysctl_sched_min_granularity
*/
static unsigned int sched_nr_latency = 8;
/*
* Minimal preemption granularity for CPU-bound tasks:
* (default: 0.75 msec * (1 + ilog(ncpus)), units: nanoseconds)
*/
unsigned int sysctl_sched_min_granularity = 750000ULL;
unsigned int normalized_sysctl_sched_min_granularity = 750000ULL;
其中的75=600/8,也就是一个0.75ms左右,当系统中
五、其它问题
对于实时任务,任务一般有一个prio,这个值可能会因为系统中的优先级倒置而发生变化,例如,当一个实时任务来获取一个由非实时任务正在持有的信号的时候,此时根据优先级翻转的原则,系统可能会暂时的提高非实时任务的优先级到实时任务,此时一个任务的优先级和调度策略可能出现不一致。例如,一个线程设置的是sched_NORMAL,但是在优先级翻转之后,它的优先级可能会转换为一个实时的优先级,例如小于100的50,但是由于在它执行完之后,还要回复原始的优先级,所以prio和其它的优先级可能会共存。
包括其它的时候为了提高交互任务的优先级,也可能微调一个任务的优先级。