一些资料
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队列里。