进程调度三 进程调度介绍

一、进程调度介绍:

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选择红黑树最左边的进程运行。随着系统时间的推移,原来左边运行过的进程慢慢的会移动到红黑树的右边,

原来右边的进程也会最终跑到最左边。因此红黑树中的每个进程都有机会运行:

    RB_tree.png

  其中进程描叙符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
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文无所谓版权,欢迎转载。

猜你喜欢

转载自blog.csdn.net/frank_zyp/article/details/85988393