1.背景
由于实时任务的优先级高于普通任务,因而为了防止cpu消耗型的实时任务一直占用cpu引发其他任务"饥饿"的情况发生,内核采用了带宽限制手段来抑制实时任务的运行时间。系统中将各个任务按层级组织成一个个任务组,组内的所有任务视为一个整体挂在一个运行队列上,而带宽限制的单位也是针对一个组来进行的。
在任务调度中带宽限制就是指一定周期内一个队列上任务可运行的最大时间,内核中使用xxx_bandwidth结构来限制任务的运行时间。针对实时任务这个结构就是:
(1)实时任务CPU使用率的检测周期
(2)Rt_period周期内实时任务允许的额定运行时间
(3)执行周期监控的高精度定时器
rt_bandwidth限制了进程组就绪队列rt_rq上的实时任务在rt_period周期内运行时间不能够超过rt_runtime;而在SMP多cpu环境中rt_bandwidth限制了系统中实时任务在rt_period周期内的cpu占用时间比例不能够超过rt_runtime/rt_period。一个典型的SMP系统中我们通常限制实时进程在1s内的最大运行时间为0.95s。
实时进程组的带宽限制需要解决两个问题:首先是何时开始限制进程组,第二是何时解除进程组的限制。下面围绕这两个方面进行分析。
2. 实时任务调度限制的实现
实时调度器会在更新进程时间统计信息的时候去检查实时进程是否运行过长时间,这是朴素的设计思想,所以首先从update_curr_rt开始分析。
2.1 实时任务的时间信息更新
(1)如果不是实时任务,那么直接返回。
(2)计算当前任务从上一次更新运行时间到现在的运行时间delta_exec。
(3)统计最大运行时间exec_max。
(4)统计当前实时任务总的运行时间sum_exec_runtime。
(5)从新赋值实时任务的起始运行时间exec_start。
(6)从当前实时任务所在的group循环到root group,更新每一个group任务队列的运行时间。前面有讲到带宽限制是针对taskgroup,所以每一个任务的更新都会导致自己所属组的运行时间rt_time的增加。
(7)根据当前任务找到所属组的任务队列:
static inline struct rt_rq *rt_rq_of_se(struct sched_rt_entity *rt_se)
{
return rt_se->rt_rq;
}
(8)把当前任务本次更新时的运行时间加入所属组的运行时间。
(9)调用sched_rt_runtime_exceeded检查当前运行的实时任务的所属组的任务队列是否超时。
(10)如果超时(比如大于950ms)那么调用resched_curr设置当前运行的实时任务的TIF_NEED_RESCHED标志,这样下一个调度时刻就可以抢占当前运行的实时任务。
2.2 实时任务的超时检查
(1)获取当前group实时运行队列的最大实时运行时间:
static inline u64 sched_rt_runtime(struct rt_rq *rt_rq)
{
if (!rt_rq->tg)
return RUNTIME_INF;
return rt_rq->rt_runtime;
}
(2)如果当前组的实时就绪队列处于throttled状态,那么直接返回该状态。
(3)如果设置的最大runtime不小于检测周期,那么直接返回0,表示不用throttle。
(4)某些场景下我们需要对实时就绪队列的最大runtime做平衡调整。(在多核情况下,其他cpu上的rt_rq还有剩余时间,可以从其他cpu的rt_rq中"借"时间)
(5)取出平衡调整后的最大runtime。
(6)如果实时就绪队列rt_rq累积的运行时间rt_time超过了最大允许的运行时间rt_runtime,那么就需要对当前的实时就绪队列做throttle。
(7)设置当前实时就绪队列的rt_throttled标志为1。
(8)在有些系统中不允许实时进程运行时间过长,那么这个时候可以在这里触发panic,具体可以在dump_throttled_rt_tasks里面调用BUG()。
(9)最后调用rt_rq_throttled再次检查throttled标志,如果没有问题就调用sched_rt_rq_dequeue把当前实时就绪队列对应的调度实体从所属的实时就绪队列上删除:
3. 实时任务调度限制的解除
前文讲到实时任务带宽限制针对的是实时任务所在的就绪队列,每一个实时任务的运行都会对所属的实时就绪队列的实时运行时间rt_time有所贡献,在实时任务时间数据更新的时候我们都会去检查rt_time有没有超过rt_runtime,如果超过就会执行throttle同时把当前实时任务所在的实时就绪队列对应的调度实体从就绪队列上删除。
从上面的逻辑中我们可以看到实时就绪队列的实时运行时间rt_time是一个累加值,我们判断是否应该执行带宽限制的一句是rt_time是否超过了rt_runtime,超过就要进行限制,否则不限制。因此rt_time不可能一直增加,一旦调度限制已经让任务得到了应有的“惩罚”,就需要解除这个限制, 内核中由struct task_group的rt_bandwidth的高精度时钟rt_period_timer来实现此功能。
3.1 rt_period_timer的初始化
首先task_group里面有对实时进程带宽限制的描述:
然后在进程组创建的时候会对rt_bandwidth进行初始化:
sched_create_group–>alloc_rt_sched_group–>init_rt_bandwidth
这里rt_period被赋值为1000000000ns,rt_runtime被赋值为950000000ns。紧接着初始化rt_period_timer,hrtimer的回调为sched_rt_period_timer
3.2 rt_period_timer触发逻辑
(1).enqueue_task流程
也就是说当有实时进程添加进就绪队列的时候触发hrtimer进行超时监控:
enqueue_task_rt
–>enqueue_rt_entity
–>__enqueue_rt_entity
–>inc_rt_tasks
–>inc_rt_group
–>start_rt_bandwidth
–>hrtimer_forward_now(&rt_b->rt_period_timer, ns_to_ktime(0));
hrtimer_start_expires(&rt_b->rt_period_timer, HRTIMER_MODE_ABS_PINNED);
可以看到这是一个立马触发hrtimer回调的流程。
(2)sched_rt_rq_enqueue流程
也就是说当我们把实时就绪队列放入其所属的就绪队列的时候需要触发hrtimer进行超时监控,当rt_rq受限解除的时候需要这个操作来重新开始超时监控。另外这里有个调度器的.rq_offline操作我们暂不分析:
.rq_offline
–>rq_offline_rt
–>__disable_runtime
–>sched_rt_rq_enqueue
–>enqueue_rt_entity
3.3 rt_period_timer回调处理
(1)设置hrtimer的超时时间为rt_period(1s),返回时钟超时的期数overrun。
(2)主要的时钟处理函数,下面详细分析。
(3)如果do_sched_rt_period_timer返回的idle等于1,表示此task_group中没有可调度的任务,这个时候时钟标志rt_period_active设置为未**。
(1)因为每一颗cpu上面都有task_group对应的rt_rq需要处理,所以这里需要for循环。
(2)根据rt_b找到cpu i上对应task_group对应的rt_rq。
(3)如果rt_rq->rt_time大于0并且当前rt_rq为受限状态,那么调用balance_runtime以尝试从其他cpu的rt_rq偷时间。
(4)获取balance后的限制时间runtime。
(5)在rt_time中抹去周期运行时间overrun*runtime,没有到一个周期则将运行时间rt_time清零。如果rt_time超过一个周期那么rt_time设置为超出一个周期多出来的时间。
(6)如果更新后的运行时间rt_time小于额定时间并且当前处于受限状态,那么清除调度受限标志,并将入队标志设置为1。
(7)如果rt_nr_running非零,证明当前rt_rq上有任务需要调度,设置idle=0。
(8)如果此周期rt_rq没有运行时间,但是rt_rq上还有就绪任务且rt_rq没有调度受限,那么设置入队标志queue=1.
(9)如果入队标志为1,这个时候我们调用sched_rt_rq_enqueue把当前的rt_rq入队,这个就是周期性清除task_group调度受限的关键操作。
(10)最后返回idle表示此task_group中是否还有就绪任务需要调度。返回1表示没有就绪任务需要调度了。
小结:从上面do_sched_rt_period_timer(rt_b, overrun)函数也可以看到队列的带宽限制的解除条件:在时钟到期后重新计算rt_rq的运行时间(也就是剩余的运行时间),如果更新后的运行时间小于一个周期的额定时间,则会解除rt_rq的调度限制rt_rq->rt_throttled = 0。
4.总结
上面简要分析了内核中如何通过带宽限制来防止实时任务无限制的占用cpu资源的原理,现将实现方式简单归纳如下:
1 带宽限制的对象是一个组,组内的任务都挂到同一个运行队列rt_rq上,最终带宽限制的实施对象就是rt_rq;
2 内核在多个关键点更新任务的信息update_curr_rt(),而更新当前任务信息后,就会检查组的运行时间是否超过带宽限制;
3 如果一个rt_rq超过带宽限制,则会标记此rt_rq调度受限,此后rt_rq上的实体将被移出队列,并且带宽限制解除前无法再加入到队列上;
4 每个任务组都维护着一个高精度时钟rt_period_timer用以定期(rt_period)更新rt_rq上的运行时间,并对"被惩罚到位"的rt_rq解除调度限制。