1. 说明
1> linux内核关于task调度这块是比较复杂的,流程也比较长,要从源码一一讲清楚很容易看晕,因此需要简化,抓住主要的一个点,抛开无关的部分才能讲清楚核心思想
2> 本篇文章主要是讲清楚在cfs公平调度算法中,CGroup如何限制cpu使用的主要过程,所以与此无关的代码一律略过
3> 本篇源码来自CentOS7.6的3.10.0-957.el7内核
4> 本篇内容以
《极简cfs公平调度算法》为基础,里面讲过的内容这里就不重复了
5> 为了极简,这里略去了CGroup嵌套的情况
2. CGroup控制cpu配置
CGroup控制cpu网上教程很多,这里就不重点讲了,简单举个创建名为test的CGroup的基本流程
1> 创建一个/sys/fs/cgroup/cpu/test目录
2> 创建文件cpu.cfs_period_us并写入100000,创建cpu.cfs_quota_us并写入10000
表示每隔100ms(cfs_period_us)给test group分配一次cpu配额10ms(cfs_quota_us),在100ms的周期内,group中的进程最多使用10ms的cpu时长,这样就能限制这个group最多使用单核10ms/100ms = 10%的cpu
3> 最后创建文件cgroup.procs,写入要限制cpu的pid即生效
3. CGroup控制cpu基本思想
1> 《极简cfs公平调度算法》中我们讲过cfs调度是以se为调度实例的,而不是task,因为group se也是一种调度实例,所以将调度实例抽象为se,统一以se进行调度
2> CGroup会设置一个cfs_period_us的时长的定时器,定时给group分配cfs_quota_us指定的cpu配额
3> 每次group下的task执行完一个时间片后,就会从group的cpu quota减去该task使用的cpu时长
4> 当group的cpu quota用完后,就会将整个group se throttle,即将其从公平调度运行队列中移出,然后等待定时器触发下个周期重新分配cpu quota后,重启将group se移入到cpu rq上,从而达到控制cpu的效果。
一句话说明CGroup的控制cpu基本思想:
进程执行完一个时间片后,从cpu quota中减去其执行时间,当quota使用完后,就将其从rq中移除,这样在一个period内就不会再调度了。
4. 极简CGroup控制cpu相关数据结构
4.1 名词解释
|
说明
|
task group
|
进程组,为了支持CGroup控制cpu,引入了组调度的概念,task group即包含所有要控制cpu的task集合以及配置信息。
|
group task
|
本文的专有名词,是指一个进程组下的task,这些task受一个CGroup控制
|
cfs_bandwidth
|
task_group的重要成员,包含了所要控制cpu的period,quota,定时器等信息
|
throttle
|
当group se在一个设定的时间周期内,消耗完了指定的cpu配额,则将其从cpu运行队列中移出,并不再调度。
注意:处于throttled状态的task仍是Ready状态的,只是不在rq上。
|
unthrottle
|
将throttle状态的group se,重新加入到cpu运行队列中调度。
|
4.2 cfs调度相关数据结构
struct cfs_rq { struct rb_root tasks_timeline; // 以vruntime为key,se为value的红黑树根节点,schedule时,cfs调度算法每次从这里挑选vruntime最小的se投入运行 struct rb_node* rb_leftmost; // 最左的叶子节点,即vruntime最小的se,直接取这个节点以加快速度 sched_entity* curr; // cfs_rq中当前正在运行的se struct rq* rq; /* cpu runqueue to which this cfs_rq is attached */ struct task_group* tg; /* group that "owns" this runqueue */ int throttled; // 表示该cfs_rq所属的group se是否被throttled s64 runtime_remaining; // cfs_rq从全局时间池申请的时间片剩余时间,当剩余时间小于等于0的时候,就需要重新申请时间片 }; struct sched_entity { unsigned int on_rq; // se是否在rq上,不在的话即使task是Ready状态也不会投入运行的 u64 vruntime; // cpu运行时长,cfs调度算法总是选择该值最小的se投入运行 /* rq on which this entity is (to be) queued: */ struct cfs_rq* cfs_rq; // se所在的cfs_rq,如果是普通task se,等于rq的cfs_rq,如果是group中的task,则等于group的cfs_rq /* rq "owned" by this entity/group: */ struct cfs_rq* my_q; // my_q == NULL表示是一个普通task se,否则表示是一个group se,my_q指向group的cfs_rq }; struct task { struct sched_entity se; }; struct rq { struct cfs_rq cfs; // 所有要调度的se都挂在cfs rq中 struct task_struct* curr; // 当前cpu上运行的task };
本文中的sched_entity定义比《极简cfs公平调度算法》中的要复杂些,各种cfs_rq容易搞混,这里讲一下cfs公平调度挑选group task调度流程(只用到了my_q这个cfs_rq),以梳理清楚其关系
1> 当se.my_q为NULL时,表示一个task se,否则是group se
2> 选择当group task3的流程
3> 选择当group task的代码
task_struct *pick_next_task_fair(struct rq *rq) { struct cfs_rq *cfs_rq = &rq->cfs; // 开始的cfs_rq为rq的cfs do { se = pick_next_entity(cfs_rq); // 《极简cfs公平调度算法》中讲过这个函数,其就是取cfs_rq->rb_leftmost,即最小vruntime的se c