本次分析的kernel代码为2.6.32-220。并且我们先不考虑SMP。当前linux的调度程序由两个调度器组成:主调度器,周期性调度器(两者又统称为核心调度器);并且每个调度器包括两个内容:调度框架(其实质就是两个函数框架)及调度器类。调度器类是实现了不同调度策略的实例,如 CFS、RT class。它们的关系如下图:
当前的内核支持两种调度器类(sched_setscheduler系统调用可修改进程的策略):CFS(公平)、RT(实时);5种调度策略:SCHED_NORAML(最常见的策略)、SCHED_BATCH(除了不能抢占外与常规任务一样,允许任务运行更长时间,更好地使用高速缓存,适合于成批处理的工作)、SCHED_IDLE(它甚至比nice 19还有弱,为了避免优先级反转使用)和SCHED_RR(循环调度,拥有时间片,结束后放在队列末)、SCHED_FIFO(没有时间片,可以运行任意长的时间);其中前面三种策略使用的是cfs调度器类,后面两种使用rt调度器类。
另外,对于调度框架及调度器类,它们都有自己管理的运行队列,调度框架只识别rq(其实它也不能算是运行队列),而对于cfs调度器类它的运行队列则是cfs_rq(内部使用红黑树组织调度实体),实时rt的运行队列则为rt_rq(内部使用优先级bitmap+双向链表组织调度实体)。下图调度器相关的几个数据结构的关系:
Rq表示调度框架上的运行队列(它本身并没有真正组织调度实体,而是它下面的cfs及rq两个成员负责组织真正的调度实体),在多核处理器上每个cpu有一个rq结构.
rq#cpu运行队列,每个cpu一个
nr_running:表示总共就绪的进程数(包括cfs,rq及正在运行的)
cpu_load[]:表示该rq所在cpu的历史load,一般有5个
load:表示当前cpu的load,这个load是它所有就绪进程的load之和(同样包括cfs,rq及正在运行的)
nr_swithces:进行上下文切换的次数
cfs:该rq所包括的cfs_rq运行队列,这个是所有cfs_rq的root
curr:当前正在运行的进程(这里不是se,注意跟cfs_rq的curr区分开来)
clock:运行队列的时钟,这个时钟是后面cfs,rq所使用的时钟,也是大多数proc显然的时间相关的时钟
cpu:该rq所属的cpu
leaf_cfs_rq_list:如果使用的cgroup来创建嵌套的group,那么这个group的cfs_rq通过该变量组织,注:每个cgroup都有一个cfs_rq
cfs_rq#cfs调度器类的运行队列
load:该cfs_rq的load,它只计算它本层下面的se的weight之和,并不是这个se的load,也不是递归到叶子节点上的所有se weight之和(理解这点非常重要),这里有两个概念要区分清楚:se的load与se的weight,之所以会有这两个区别是因为组调度的存在,对于一个组它本身也是一个se,它也有一个cfs_rq,所以对于组它的se->weight其实就是我们设置cgroup的cpu.shares的一个均值(cpu.shares是保存在tg->shares,而se->weight是通过update_cfs_shares计算出来的,如果它下面的se在一个cpu里,那么它们就相等,否则…还不清楚到底为什么那样算的?),而它的cfs_rq的load,则是它下面所有se的weight之和;对于普通的进程,则它本身没有cfs_rq(它有所属的cfs_rq),所以它没有cfs_rq load,只有weight
nr_running:就绪的进程数,只包括本层的(包括正在运行的)
h_nr_running:只对于组才有效,包括底层所有cfs_rq的nr_running之和
exec_lock:该cfs_rq总共占用的cpu时间(物理),只累计本层
min_vruntime:用于调整se的vruntime,它是递增的,但不一定是该cfs_rq里所有se最小
tasks_timeline:该cfs_rq的红黑树root
rb_leftmost:红黑树的最左边节点
curr:当前运行的se(对于组虽然它不会在cpu上运行,但是当它的下层有一个task在cpu上运行,那么它所在的cfs_rq就把它当做是该cfs_rq上当前正在运行的se)
rq:该cfs_rq当前所attach的cpu rq
tg:该cfs_rq所属的task group,即所属的组,通过该成员可以找到该cfs_rq对应的组在该cpu上所属的se,即该cfs_rq自身的se
sched_entity#调度实体
load:调度实体自身的load,对于普通进程这个load跟优先级成正比,优先级高的,load大,通过prio_to_weight[]表可以查找到它们的对应关系(注意与cfs_rq的load区分开)
run_node:红黑树节点
on_rq:表示是否在运行队列或正在执行(当前执行的进程是不保存在红黑树里,但它的on_rq还是标志着的,记住这个也很关键)
exec_start:这个并不是表示进程开始执行的时候,而是每次update_curr都会更新该时钟为当前rq的clock,它主要用于计算上次执行update_curr到这次再执行,总共发的cpu clock,然后再把这个差值加到sum_exec_runtime
sum_exec_runtime:进程总共执行的cpu clock(占用cpu的物理时间)
prev_sum_exec_runtime:上次该进程被调度时已经占用的cpu时间(每次在调度一个新的进程时都会把它的se->prev_sum_exec_runtime = se->sum_exec_runtime),所以sum_exec_runtime- prev_sum_exec_runtime就是这次调度占用cpu的clock
vruntime:虚拟时间(实现CFS最关键的东西),简单的理解它是执行的cpu clock的一个虚拟值,这个变换与进程的权重成反比
parent:该se的上级se(只对组调度有用)
cfs_rq:该se就绪时所属的cfs_rq,在不同cpu上该值是不一样的(即cfs_rq_of(se)找到的是se所在的父se的my_q)
my_q:该se下面的管理的se组成的cfs_rq(只对组调度有用),这个与cfs_rq的区别很重要,它是该se本身所管理的所有下级se所组成的运行队列,而cfs_rq则是该se所属的父级运行队列
task_group#组结构体,每个group都有一个task_group结构
se:该tg在每个cpu上的se
cfs_rq:该tg在每个cpu上的cfs_rq
shares:我们用cgroup设置cpu.shares的值
对于实时调度类也是一样的,它也是支持组调度的,它的运行队列是使用rt_prio_array active组织的,即bitmap+lists,我们在介绍RT类的时候再详细介绍。这些字段的含义也是proc字段的意思,所以理解这些对于理解proc信息很有帮助。