一些资料
cpu带宽控制的原理是在给定周期内(cpu.cfs_period_us)内限制进程组的运行时间(cpu.cfs_quota_us),这两个参数值通过cpu子系统来设置。
cfs_rq相关的两个成员是 runtime_remaining 和 runtime_expires ,前者是当前进程组的剩余时间片,后者是时间片的到期时间。
内核的做法是:
- 每次进程切换的时候更新当前进程的运行时间,检查进程时钟带宽是否超过阈值。
- 通过一个定时器,每隔固定的cpu.cfs_period_us周期,更新进程组的时钟带宽
进程切换的时候会统计cpu的实际运行时间,如下:
/**
put_prev_entity()
update_curr()
account_cfs_rq_runtime()
**/
static void __account_cfs_rq_runtime(struct cfs_rq *cfs_rq,
unsigned long delta_exec) {
/* dock delta_exec before expiring quota (as it could span periods) */
// 当前cfs_rq的剩余时间片减少
cfs_rq->runtime_remaining -= delta_exec;
expire_cfs_rq_runtime(cfs_rq);
if (likely(cfs_rq->runtime_remaining > 0))
return;
/*
* if we're unable to extend our runtime we resched so that the active
* hierarchy can be throttled
*/
if (!assign_cfs_rq_runtime(cfs_rq) && likely(cfs_rq->curr))
resched_task(rq_of(cfs_rq)->curr);
}
注意在cfs带宽控制里,所有的时间都是基于真实时钟而不是虚拟时钟来计算的。与进程组优先级无关。
检查进程组的时钟带宽是否<=0,如果是则throttle
/*
调用栈如下
put_prev_entity()
check_cfs_rq_runtime()
*/
/* conditionally throttle active cfs_rq's from put_prev_entity() */
static void check_cfs_rq_runtime(struct cfs_rq *cfs_rq)
{
if (likely(!cfs_rq->runtime_enabled || cfs_rq->runtime_remaining > 0))
return;
/*
* it's possible for a throttled entity to be forced into a running
* state (e.g. set_curr_task), in this case we're finished.
*/
if (cfs_rq_throttled(cfs_rq))
return;
// 如果cfs_rq的cfs_rq->runtime_remaining <= 0,则throttle
throttle_cfs_rq(cfs_rq);
}
throttle_cfs_rq所做的事情就是将当前进程组dequeue出cpu的运行队列,并加入到throttled_list里。进程组的cfs_rq既然不在cpu的运行队列里,自然就不会被调度运行了,然后必须等到下一个周期才能重新进入到cpu队列里。