概念部分只讲解
CFS
调度器
CFS调度器的重点
基本概念
Linux 2.6
版本在我看来是一个比较完美的版本,而2.4
和2.6
最大的差异就在于CFS
调度器的引入
由于优先级的引入,CFS
对于cpu
资源的分配(即实际运行时间runtime
)是根据权重来进行分配的,权重weight
和优先级nice
的映射如下,优先级和权重呈反比
static const int prio_to_weight[40] = {
/* -20 */ 88761, 71755, 56483, 46273, 36291,
/* -15 */ 29154, 23254, 18705, 14949, 11916,
/* -10 */ 9548, 7620, 6100, 4904, 3906,
/* -5 */ 3121, 2501, 1991, 1586, 1277,
/* 0 */ 1024, 820, 655, 526, 423,
/* 5 */ 335, 272, 215, 172, 137,
/* 10 */ 110, 87, 70, 56, 45,
/* 15 */ 36, 29, 23, 18, 15,
};
实际运行时间runtime
的计算公式则为
r u n t i m e = s c h e d u l e p e r i o d × w e i g h t i / ∑ i = 1 n w e i g h t i ( i = 1 , 2... n ) runtime = scheduleperiod \times weight_i / \sum_{i=1}^{n} weight_i(i=1,2...n) runtime=scheduleperiod×weighti/i=1∑nweighti(i=1,2...n)
此处设置了一个虚拟运行时间vruntime
v r u n t i m e = r u n t i m e × ( 1024 / w e i g h t i ) vruntime = runtime\times(1024/weight_i) vruntime=runtime×(1024/weighti)
化简可得每一个调度实体的vruntime
不由自身的权重改变而改变,因此从宏观上来看每个调度周期时每一个调度实体的vruntime
应当是一样的,这是理想状态下的
v r u n t i m e = s c h e d u l e p e r i o d × ( 1024 / ∑ i = 1 n w e i g h t i ( i = 1 , 2... n ) ) vruntime = scheduleperiod \times (1024/\sum_{i=1}^{n} weight_i(i=1,2...n)) vruntime=scheduleperiod×(1024/i=1∑nweighti(i=1,2...n))
而某一调度实体由于某些原因导致进入阻塞或睡眠态,此时便会主动将时间片让出去,导致其vruntime
暂时不变,而其他调度实体获得了该时间片开始运行,导致其vruntime
增加,此时便会形成不对等,这是不公平的,因此需要在下一进程切换时调度vruntime
最小的进程
为什么让优先级高的和优先级低的分配不同的
runtime
却说完全公平呢?
因为该公平是在vruntime
的逻辑上,而不是runtime
的逻辑上,cfs
保证了每一个调度实体在vruntime
的相等,如果有较小的vruntime
就优先调度它
优先级较高的runtime
较大,优先级较低的runtime
较小,但其vruntime
是一样的,因此在这种情况下优先级较低的实际上是时钟有了更高的衰减率
一些问题
新进程的vruntime
的初始值是不是0
?
- 子进程在创建时,
vruntime
初值首先被设置为min_vruntime
- 如果
sched_features
中设置了START_DEBIT
位,vruntime
会在min_vruntime
的基础上再增大一些 - 设置完子进程的
vruntime
之后,检查sched_child_runs_first
参数,如果为1的话,就比较父进程和子进程的vruntime
,若是父进程的vruntime
更小,就对换父、子进程的vruntime
,这样就保证了子进程会在父进程之前运行
休眠进程的vruntime
的值一直保持不变吗?
在休眠进程被唤醒时重新设置vruntime
值,以min_vruntime
值为基础,给予一定的补偿,但不能补偿太多
进程占用的时间片可以无穷小吗?
CFS
设定了进程占用CPU的最小时间值, sched_min_granularity_ns
,正在CPU上运行的进程如果不足这个时间是不可以被调离CPU
的
liuzixuan@10-60-73-159:~$ cat /proc/sys/kernel/sched_min_granularity_ns
1500000
进程从一个CPU
迁移至另外一个CPU
的时候vruntime
会变化吗?
当进程从一个CPU的运行队列中出来时,它的vruntime
要减去队列的min_vruntime
值; 而当进程加入另一个CPU
的运行队列时,它的vruntime
要加上该队列的min_vruntime
值。 这样,进程从一个CPU
迁移到另一个CPU
之后,vruntime
保持相对公平
vruntime
无限累加,产生溢出怎么办?
红黑树中的key
不是vruntime
而是vruntime-min_vruntime
,min_vruntime
是红黑树中最小的key
,减去一个最小的vruntime
将所有进程的key
围绕在最小vruntime
的周围,换句话来说就是,只比较相对大小
static inline int less(u32 left, u32 right)
{
return (less_eq(left, right) && (mod(right) != mod(left)));
}
源码和补充
调度类和调度策略
Linux
调度类:按所需分配的计算能力, 向系统中每个进程提供最大的公正性
fair_sched_class
:CFS
完全公平调度器idle_sched_class
:每个处理器有一个空闲线程,即0
号线程rt_sched_class
:为每个调度优先级维护一个队列
struct sched_class {
const struct sched_class *next;
void (*enqueue_task) (struct rq *rq, struct task_struct *p, int wakeup);
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int sleep);
void (*yield_task) (struct rq *rq);
void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int sync);
struct task_struct * (*pick_next_task) (struct rq *rq);
void (*put_prev_task) (struct rq *rq, struct task_struct *p);
#ifdef CONFIG_SMP
int (*select_task_rq)(struct task_struct *p, int sync);
unsigned long (*load_balance) (struct rq *this_rq, int this_cpu,
struct rq *busiest, unsigned long max_load_move,
struct sched_domain *sd, enum cpu_idle_type idle,
int *all_pinned, int *this_best_prio);
int (*move_one_task) (struct rq *this_rq, int this_cpu,
struct rq *busiest, struct sched_domain *sd,
enum cpu_idle_type idle);
void (*pre_schedule) (struct rq *this_rq, struct task_struct *task);
int (*needs_post_schedule) (struct rq *this_rq);
void (*post_schedule) (struct rq *this_rq);
void (*task_wake_up) (struct rq *this_rq, struct task_struct *task);
void (*set_cpus_allowed)(struct task_struct *p,
const struct cpumask *newmask);
void (*rq_online)(struct rq *rq);
void (*rq_offline)(struct rq *rq);
#endif
void (*set_curr_task) (struct rq *rq);
void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
void (*task_new) (struct rq *rq, struct task_struct *p);
void (*switched_from) (struct rq *this_rq, struct task_struct *task,
int running);
void (*switched_to) (struct rq *this_rq, struct task_struct *task,
int running);
void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
int oldprio, int running);
#ifdef CONFIG_FAIR_GROUP_SCHED
void (*moved_group) (struct task_struct *p);
#endif
};
static const struct sched_class fair_sched_class; // 公开调度类
static const struct sched_class idle_sched_class; // 空闲调度类
static const struct sched_class rt_sched_class; // 实时调度类
Linux
调度策略:决定什么时候以怎么样的方式选择一个新进程占用CPU
运行
SCHED_NORMAL
:普通进程调度策略,使调度实体通过cfs
调度器运行SCHED_FIFO
:实时进程调度策略,先进先出调度算法SCHED_RR
:实时进程调度策略,时间片轮转算法SCHED_BATCH
:普通进程调度策略,批量处理,使调度实体通过cfs
调度器运行SCHED_IDLE
:普通进程调度策略,使调度实体以最低优先级通过cfs
调度器运行
#define SCHED_NORMAL 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#define SCHED_BATCH 3
#define SCHED_IDLE 5
可以通过
sched_getscheduler()
系统调用获得某一进程调度策略
优先级分类
关于优先级问题,优先级一般分为静态优先级和动态优先级
- 静态优先级:用
100
到139
表示普通进程的静态优先级,用来估价系统中这个进程和其他普通进之间调度的程度,本质上决定了进程的基本时间片 - 动态优先级:用
100
到139
表示普通进程的动态优先级,其是调度程序在选择新进程来运行的时候使用的数
多处理器系统 rq的平衡
一个原则:任何一个可运行进程都不可能同时出现在两个或多个运行队列中
调度域:是一个cpu
集合,它的工作量应当由内核保持平衡,其组成类似于基数树,每个调度域被依次划分为一个或多个组,每个组待办调度域的一个cpu
子集,工作量的平衡总是在调度域的组之间来完成
系统中所有物理cpu
的sched_domain
描述符都放在每cpu
变量phys_domains
中
static DEFINE_PER_CPU(struct static_sched_domain, phys_domains);
它们的初始化在各个机器目录中
/* sched_domains SD_NODE_INIT for SGI IP27 machines */
#define SD_NODE_INIT (struct sched_domain) {
\
.parent = NULL, \
.child = NULL, \
.groups = NULL, \
.min_interval = 8, \
.max_interval = 32, \
.busy_factor = 32, \
.imbalance_pct = 125, \
.cache_nice_tries = 1, \
.flags = SD_LOAD_BALANCE \
| SD_BALANCE_EXEC \
| SD_WAKE_BALANCE, \
.last_balance = jiffies, \
.balance_interval = 1, \
.nr_balance_failed = 0, \
}