摘要
什么是带宽控制?简单来讲就是控制一个任务组在给定周期时间内可消耗的CPU时间,如果在周期内消耗的CPU时间超额,则限制这个任务组内的任务调度,直到下一个周期。
本文要讨论的CFS带宽控制,它是专门针对CFS调度策略而言的;相应的,对于RT调度策略还有RT带宽控制。
原理
CFS带宽控制是基于公平调度任务组实现的,因而使用时需要打开CONFIG_FAIR_GROUP_SCHED内核配置;一个任务组通过运行队列cfs_rq组织本组内的任务(实际上是调度实体),每个CPU在组内都有一个对应的cfs_rq运行队列。
带宽相关参数
我们通常意义上所讲的带宽概念是 带宽=fun{数量,周期},这和CPU带宽概念也是符合的。任务组的带宽由两个因素决定:quota和period。其中,period是周期(单位us),而quota则是任务组在周期内的可用CPU时间。一旦确定了quota和period,一个任务组的CPU带宽也就确定了。
CFS带宽控制的关键就是要控制任务的运行时间,若任务组内在周期内消耗的CPU时间超过了quota,内核则为组内的运行队列设置一个“throttled”标志,以表示此队列在本周期限制调度,直到下一个周期才能得以恢复。
除了这两个参数外,还有几个参数需要说明:
runtime:表示任务组周期内剩余的可运行时间 |
参数runtime是任务组的剩余CPU时间,它由组内各个cfs_rq运行队列所共享。理论上一个周期结束时它的值将重新置为quota,但是为了性能方面考虑,内核实际只在在任务组中有运行队列发生“throttled”才去更新任务组的runtime_expires和runtime。
任务组组内各个cfs_rq上的任务对CPU时间的需求量不一致,为了保证各个cfs_rq上的CPU时间分配的相对合理(即不浪费,又不太频繁的申请),各个CPU上的cfs_rq每次只从runtime中申请“一小片”时间。这个“时间片”可以通过/proc/sys/kernel/sched_cfs_bandwidth_slice_us(默认值为5ms)接口来调整。
Slice取值的大小随需求而定:slice越大各个cfs_rq每次可运行的时间越长,在总的runtime有限的情况下一个周期内分配slice的频率就越低;而slice值越小,各个cfs_rq的时间粒度也越小,这样可以更精确的分配给各个cfs_rq运行队列的时间。
CGROUP的cpu子系统为CFS带宽控制提供了一些接口文件,用于调整任务组的带宽控制参数。
带宽控制参数的cgroup接口
-----------------------
参数quota与period可通过cgroup中的cpu子系统来进行控制。
cpu.cfs_quota_us: 时间份额。周期内可用时间(微秒),默认值是-1,即没有限制 |
下面来详细了解一下这些参数的含义:
参数cpu.cfs_quota_us为-1表示这个任务组没有带宽限制,早期的CFS调度中采用的就是这种无带宽限制方式。
往cpu.cfs_quota_us文件写入正值将设置此任务组的CPU带宽限制,写入的值即为任务组的quota;而向cpu.cfs_quota_us写入负数值则是取消任务组的带宽限制;如果任务组已经处于“throttled”限制状态,则重新设置任务组的带宽会消除掉它的“throttled”状态,让任务组重回非调度限制状态。
任务组的quota和period的最小值是1ms,所以往cpu.cfs_quota_us和cpu.cfs_period_us写入的正数不能小于1000;而period的最值为1s,即cpu.cfs_period_us最大值为1000000。除了这些限制外,CFS带宽控制中的参数还受到任务组层级的影响,这些将在稍后更详细的讲述。
查看CFS带宽控制信
-----------------------
任务组的CFS带宽控制相关的信息可通过cgroup中的(cpu子系统)的cpu.stat文件接口查询,它是一个只读文件。
cpu.stat: |
任务组层级的限制
-----------------------
前面提到的cgroup的cpu.cfs_quota_us和cpu.cfs_period_us接口可以将一个任务组的带宽控制在:max(c_i) <= C 。其中,C表示parent任务组的带宽;而c_i表示某个子组的带宽,即子组中最大带宽不能超过parent组的带宽。但是允许子组设置的带宽总额大于parent层级的带宽,即:Sum (c_i) >= C。
任务组进入到调度限制状态的情况有两种:
任务组在一个period内用完自己的quota
parent任务组在它的周期内用完quota
在上面的情况2)中,即使子组还有剩余的runtime也会限制调度,直到parent重新分配到新的runtime。
相关示例
-----------------------
为了方便讲述,我将周期内一个cpu所能提供的计算资源称作一个cpu时间;例如在一个4核系统中,一个周期内这个系统总共可以提供4个cpu时间。
1. 任务组的带宽设置为%100
# echo 250000 > cpu.cfs_quota_us /* quota = 250ms */ |
上面的设置可以为任务组在周期内提供1个cpu的带宽资源,即总的cpu占用率为100%。
2. 任务组的带宽配置为200%
# echo 1000000 > cpu.cfs_quota_us /* quota = 1000ms */ |
上面的配置可以为任务组在周期内提供2个cpu的带宽资源,即周期内总的cpu占用率总和最多可达到200%。
在此带宽比例配置前提下,周期越长,任务组的吞吐量越大。
3. 任务组的带宽为限制为%20
# echo 10000 > cpu.cfs_quota_us /* quota = 10ms */ |
上面的配置限制任务组在周期内只能提供20%的cpu带宽资源,即周期内总的cpu占用率总和不能超过20%。在这种带宽比例的情况下,周期越短响应延迟也就越小。
参考:Documentation/scheduler/sched-bwc.txt