一、进程调度介绍:
1、进程调度的产生:
进程从使用资源方面可以分为如下两类,不管是I/O还是CPU受限类的进程,CPU都希望再尽可能短的时间
完成更多的工作,但另一方面,又希望尽可能的减少资源(I/O或CPU)的消耗,这两则之间存在矛盾,所以进程
的调度管理就是来协调两者之间的冲突。
类型 | 别称 | 描述 | 示例 |
---|---|---|---|
I/O受限型 | I/O密集型 | 频繁的使用I/O设备, 并花费很多时间等待I/O操作的完成 | 数据库服务器, 文本编辑器 |
CPU受限型 | 计算密集型 | 花费大量CPU时间进行数值计算 | 图形绘制程序 |
2、进程调度器的种类:
(1)RT scheduler:实时调度器
(2)Deadline scheduler:deadline调度器
(3)CFS scheduler:完全公平调度器
(4)Idle scheduler:idle调度器
3、进程调度类:
(1)调度类的介绍:
系统核心调度代码会通过struct sched_class
结构体的成员调用具体调度类的核心算法,其结构如下:
struct sched_class {
const struct sched_class *next;//执行下一个具体的调度类
void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);//向该调度器管理的runqueue中添加一个进程
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);//从该调度器管理的runqueue中删除一个进程
void (*yield_task) (struct rq *rq);
bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);
void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);//检查抢占标记TIF_NEED_RESCHED flag
struct task_struct * (*pick_next_task) (struct rq *rq,
struct task_struct *prev);从runqueue中选择一个最适合运行的task。
void (*put_prev_task) (struct rq *rq, struct task_struct *p);
void (*update_curr) (struct rq *rq);
}
(2)具体调度类:
调度器类 | 描叙 | 调度策略 |
stop_sched_class | 优先级最高的线程,会中断所有其他线程,且不会被其他任务打断 | 无 |
dl_sched_class | deadline调度器 | SCHED_DEADLINE |
rt_sched_class | 实时调度器 | SCHED_FIFO、SCHED_RR |
fair_sched_clas | 完全公平调度器 | SCHED_NORMAL、SCHED_BATCH |
idle_sched_class | idle调度器 | SCHED_IDLE |
调度类的优先级为:
stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
以完全公平调度器fair_sched_class举例如下:
const struct sched_class fair_sched_class = {
.next = &idle_sched_class,//指向下一优先级的低调类
.pick_next_task = pick_next_task_fair,//选择一个合适的进程执行
.update_curr = update_curr_fair,//更新当前正在运行的调度实体的运行时间信息
.enqueue_task = enqueue_task_fair,
.dequeue_task = dequeue_task_fair,
......
}
4、调度实体:
struct task_struct {
struct sched_entity se;//进程调度的实体
struct sched_rt_entity rt;
struct sched_dl_entity dl;
}
struct sched_entity {
struct load_weight load; //权重信息
struct rb_node run_node;//CFS调度器的每个就绪队列维护了一颗红黑树
struct list_head group_node;//组调度节点
unsigned int on_rq;//调度实体se加入就绪队列后,on_rq置1。从就绪队列删除后,on_rq置0
};
5、权重计算:
进程调度的权重和进程的优先级有相关性,进程优先级分为以下几种:
- 静态优先级:(定义在进程描述符中的:static_prio)
- 动态优先级:(定义在进程描述符中的:prio)
- 实时优先级:(定义在进程描述符中的:rt_priority)
其中静态优先级和nice之间的关系如下:
#define MAX_RT_PRIO 100
static_prio=MAX_RT_PRIO+nice+20
CFS调度器针对优先级提出了nice值的概念,其实和权重是一一对应的关系。nice的取值范围为:[-20,19],
数值越小代表优先级越大,同时也意味着权重值越大,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,
};
linux内核查找如上prio的函数如下:
static void set_load_weight(struct task_struct *p)
{
int prio = p->static_prio - MAX_RT_PRIO;
struct load_weight *load = &p->se.load;
/*
* SCHED_IDLE tasks get minimal weight:
*/
if (idle_policy(p->policy)) {
load->weight = scale_load(WEIGHT_IDLEPRIO);
load->inv_weight = WMULT_IDLEPRIO;
return;
}
load->weight = scale_load(prio_to_weight[prio]);
load->inv_weight = prio_to_wmult[prio];
}
6、虚拟运行时间vruntime:
CFS调度器的目标是保证每一个进程的完全公平调度。CFS引入了虚拟时间的概念,CFS保证每个进程运行的
虚拟时间是相等的即可保证公平调度。
- vruntime表示进程虚拟的运行时间;
- delta_exec表示实际运行的时间;
- nice_0_weight表示nice为0的权重值;
- weight表示进程的权重值;
将实际时间转换为vruntime的函数为:calc_delta_fair
static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
{
if (unlikely(se->load.weight != NICE_0_LOAD))
delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
return delta; //如果是NICE_0_LOAD则返回真实时间
}
/*
* delta_exec * weight / lw.weight
* OR
* (delta_exec * (weight * lw->inv_weight)) >> WMULT_SHIFT
*
* Either weight := NICE_0_LOAD and lw \e prio_to_wmult[], in which case
* we're guaranteed shift stays positive because inv_weight is guaranteed to
* fit 32 bits, and NICE_0_LOAD gives another 10 bits; therefore shift >= 22.
*
* Or, weight =< lw.weight (because lw.weight is the runqueue weight), thus
* weight/lw.weight <= 1, and therefore our shift will also be positive.
*/
static u64 __calc_delta(u64 delta_exec, unsigned long weight, struct load_weight *lw)
{
u64 fact = scale_load_down(weight);
int shift = WMULT_SHIFT;
__update_inv_weight(lw);
if (unlikely(fact >> 32)) {
while (fact >> 32) {
fact >>= 1;
shift--;
}
}
/* hint to use a 32x32->64 mul */
fact = (u64)(u32)fact * lw->inv_weight;
while (fact >> 32) {
fact >>= 1;
shift--;
}
return mul_u64_u32_shr(delta_exec, fact, shift);
}
7、就绪队列(runqueue):
系统中每个CPU都会有一个全局的就绪队列(cpu runqueue),使用struct rq
结构体描述,每一个调度类也
有属于自己管理的就绪队列。
struct rq {
/* runqueue lock: */
raw_spinlock_t lock;
/*
* nr_running and cpu_load should be in the same cacheline because
* remote CPUs use both these fields when doing load calculation.
*/
unsigned int nr_running;
struct cfs_rq cfs;
struct rt_rq rt;
struct dl_rq dl;
......
}
/* CFS-related fields in a runqueue */
struct cfs_rq {
struct load_weight load;//就绪队列权重
unsigned int nr_running, h_nr_running;//就绪队列上调度实体的s数目
u64 min_vruntime;//所有调度实体的最小虚拟时间
......
}
CFS维护了一个按照虚拟时间排序的红黑树,所有可运行的调度实体按照p->se.vruntime排序插入红黑树,
CFS选择红黑树最左边的进程运行。随着系统时间的推移,原来左边运行过的进程慢慢的会移动到红黑树的右边,
原来右边的进程也会最终跑到最左边。因此红黑树中的每个进程都有机会运行:
\
其中进程描叙符task_struct、 进程调度实体sched_entity、进程运行队列cfs_rq、CFS管理的红黑树rb_node,
可以用下图清晰的描叙(借鉴自wowo):
二、总结
1、每个CPU都有一个通用就绪队列struct rq;
2、每个进程task_struct都包含一个调度实体struct sched_entity se结构体;
3、每个通用就绪队列数据结构中包含有CFS rq、RT rq、DL rq(cfs_rq = &rq->cfs);
4、每个调度实体se包含一个权重struct load_weight load结构体;
5、每个调度实体se有一个vruntime成员表示该调度实体的虚拟时间;
6、每个调度实体se有一个on_rq表示该调度实体是否在就绪队列中接受调度;
7、每个CFS就绪队列中内嵌一个权重struct load_weight load结构体;
8、每个CFS就绪队列中有一个min_vruntime来跟踪该队列红黑树中最小的vruntime;
9、task_struct数据结构中,on_cpu表示进程是否正在执行状态中;on_rq表示进程的调度状态;
调度实体se中on_rq成员表示调度实体是否在就绪队列中接受调度。
参考链接:
http://www.wowotech.net/process_management/447.html
作者:frank_zyp
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文无所谓版权,欢迎转载。