前言
组调度(task_group)是使用Linux cgroup(control group)的cpu子系统来实现的,可以将进程进行分组,按组来分配CPU资源等。比如,看一个实际的例子:A和B两个用户使用同一台机器,A用户16个进程,B用户2个进程,如果按照进程的个数来分配CPU资源,显然A用户会占据大量的CPU时间,这对于B用户是不公平的。组调度就可以解决这个问题,分别将A、B用户进程划分成组,并将两组的权重设置成占比50%即可。
带宽(bandwidth)控制是用于控制用户组(task_group)的CPU带宽,通过设置每个用户组的限额值,可以调整CPU的调度分配。在给定周期内,当用户组消耗CPU的时间超过了限额值,该用户组内的任务将会受到限制,直到下一个周期。
提示:以下是本篇文章正文内容,下面案例可供参考
一、设计原理
先看一下/sys/fs/cgroup/cpu下的内容吧:
(1) 有两个关键的字段:cfs_period_us和cfs_quota_us,这两个与cfs_bandwidth息息相关;
(2) period表示周期,quota表示限额,也就是在period期间内,用户组的CPU限额为quota值,当超过这个值的时候,用户组将会被限制运行(throttle),等到下一个周期开始被解除限制(unthrottle);
如果使用CPU bandwith control,需要配置CONFIG_FAIR_GROUP_SCHED和CONFIG_CFS_BANDWIDTH选项。该功能是限制一个组的最大使用CPU带宽。通过设置两个变量quota和period,period是指一段周期时间,quota是指在period周期时间内,一个组可以使用的CPU时间限额。当一个组的进程运行时间超过quota后,就会被限制运行,这个动作被称作throttle。直到下一个period周期开始,这个组会被重新调度,这个过程称作unthrottle。
总结一下就是,cfs_bandwidth就像是一个全局时间池(时间池管理时间,类比内存池管理内存)。每个group cfs_rq如果想让其管理的红黑树上的调度实体调度,必须首先向全局时间池中申请固定的时间片,然后供其进程消耗。当时间片消耗完,继续从全局时间池中申请时间片。终有一刻,时间池中已经没有时间可供申请。此时就是throttle cfs_rq的大好时机。
二、数据结构
每个task_group都包含cfs_bandwidth结构体,主要记录和管理时间池的时间信息。
struct cfs_bandwidth {
#ifdef CONFIG_CFS_BANDWIDTH
raw_spinlock_t lock;
ktime_t period; //周期值;
u64 quota, runtime; //quota: 限额值;runtime: 记录限额剩余时间,在每次定时器回调函数中更新值为quota;
s64 hierarchical_quota; //层级管理任务组的限额比率;
u64 runtime_expires; //每个周期的到期时间;
int idle, period_active; //idle:空闲状态,不需要运行时分配;period_active: 周期性计时已经启动;
struct hrtimer period_timer, slack_timer; //period_timer: 高精度周期性定时器,用于重新填充运行时间消耗;
//slack_timer: 延迟定时器,在任务出列时,将剩余的运行时间返回到全局池里;
struct list_head throttled_cfs_rq; //所有被throttle的cfs_rq挂入此链表,在定时器的回调函数中便利链表执行unthrottle cfs_rq操作;
/* statistics */
int nr_periods, nr_throttled; //统计值;
u64 throttled_time; //统计值;
#endif
};
struct cfs_rq结构中相关字段如下:
struct cfs_rq {
#ifdef CONFIG_FAIR_GROUP_SCHED
struct rq *rq; /* cfs_rq依附的cpu runqueue,每个CPU有且仅有一个rq运行队列。 */
struct task_group *tg; /* cfs_rq所属的task_group。 */
#ifdef CONFIG_CFS_BANDWIDTH
int runtime_enabled; /* 该就绪队列是否已经开启带宽限制,默认带宽限制是关闭的,如果带宽限制使能,runtime_enabled的值为1。 */
u64 runtime_expires; /* 周期计时器到期时间 */
s64 runtime_remaining; /* cfs_rq从全局时间池申请的时间片剩余时间,当剩余时间小于等于0的时候,就需要重新申请时间片。 */
u64 throttled_clock, throttled_clock_task; /* 当cfs_rq被throttle的时候,方便统计被throttle的时间,需要记录throttle开始的时间。 */
u64 throttled_clock_task_time;
int throttled, throttle_count; /* throttled:如果cfs_rq被throttle后,throttled变量置1,unthrottle的时候,throttled变量置0;throttle_count:由于task_group支持嵌套,当parent task_group的cfs_rq被throttle的时候,其chaild task_group对应的cfs_rq的throttle_count成员计数增加。 */
struct list_head throttled_list; /* 被throttle的cfs_rq挂入cfs_bandwidth->throttled_cfs_rq链表。 */
#endif /* CONFIG_CFS_BANDWIDTH */
#endif /* CONFIG_FAIR_GROUP_SCHED */
};