进程调度源码分析之CFS:
在文章开始之前想要说明一下,文中内容主要参考《奔跑吧Linux内核》入门篇及卷一部分,源码的注释部分参考了gitte上大神的源码注释(Zhe Qiao/linux 5.0注释)链接如下:https://gitee.com/residual_nozzle_on_dust/linux5-0-comments/tree/master

1.1、算法思想:
1.1.1、O(1)的缺陷:
O(1)调度器在处理 交互式进程时依然存在问题,交互式场景下导致交互式进程反应缓慢。故引入CFS完全公平算法。
1.1.2、CFS调度算法的思想:
CFS抛弃了以往的算法:
- 使用固定时间片;
- 固定调度周期;
采用了进程权重值的比重来计算实际运行时间;
理想状态下每个进程都能获得相同的时间片,并且同时运行在CPU上,但实际上一个CPU同一时刻运行的进程只能有一个。也就是说,当一个进程占用CPU时,其他进程就必须等待。CFS为了实现公平,必须惩罚当前正在运行的进程,以使那些正在等待的进程下次被调度。
为了实现这种公平,便引入虚拟时间(vruntime)、真实时间(real runtime)的概念。
- 虚拟时间:实际运行时间相对NICE值为0的权重的比例值;
- 真实时间:物理时钟下运行的时间;
1.1.3:虚拟时间及选择下一个进程:
- 虚拟运行时间是通过进程的实际运行时间和进程的权重(weight)计算出来的。

- vruntime计算公式:

- CFS中的就绪队列是一棵以vruntime为键值的红黑树,虚拟时间越小的进程越靠近整个红黑树的最左端。因此,调度器每次选择位于红黑树最左端的那个进程,该进程的vruntime最小;

1.2、源码分析:
1.2.1、 load_weight():
负荷权重用struct load_weight数据结构来表示, 保存着进程权重值weight。定义在/include/linux/sched.h中,具体内容如下:
struct load_weight {
unsigned long weight;//weight是调度实体的权重;
u32 inv_weight;//inverse weight,权重的一个中间计算结果;
};
load_weight()函数内置在通用的调度实体sched_entity结构体中
struct sched_entity {
/* For load-balancing: */
struct load_weight load;//内置了load_weight结构用于保存当前调度实体的权重
struct rb_node run_node;
struct list_head group_node;
unsigned int on_rq;
u64 exec_start;
u64 sum_exec_runtime;
u64 vruntime;
u64 prev_sum_exec_runtime;
u64 nr_migrations;
struct sched_statistics statistics;
#ifdef CONFIG_FAIR_GROUP_SCHED
int depth;
struct sched_entity *parent;
/* rq on which this entity is (to be) queued: */
struct cfs_rq *cfs_rq;
/* rq "owned" by this entity/group: */
struct cfs_rq *my_q;
/* cached value of my_q->h_nr_running */
unsigned long runnable_weight;
#endif
#ifdef CONFIG_SMP
struct sched_avg avg;
#endif
}

本文详细解读了CFS(CompletelyFairScheduler)调度算法,介绍了其O(1)问题的局限性,以及如何通过虚拟时间和真实时间实现公平调度。重点剖析了load_weight结构和关键函数如schedule(),__schedule(),pick_next_task()的源码工作原理。
最低0.47元/天 解锁文章
1639






