一步一步学linux操作系统: 12 进程调度一_调度策略与调度类介绍

调度策略与调度类

Linux 中进程大概可以分成两种:

  • 实时进程
    需要尽快执行返回结果,优先级会比较高
  • 普通进程
    大部分的进程都是这种,按照正常流程完成

调度策略

task_struct 成员变量
\include\linux\sched.h

unsigned int policy;

在这里插入图片描述
几个定义
\include\linux\sched.h

#define SCHED_NORMAL    0
#define SCHED_FIFO    1
#define SCHED_RR    2
#define SCHED_BATCH    3
#define SCHED_IDLE    5
#define SCHED_DEADLINE    6

在这里插入图片描述

优先级

task_struct 成员变量
\include\linux\sched.h

int prio, static_prio, normal_prio;
unsigned int rt_priority;

在这里插入图片描述
优先级就是一个数值

  • 优先级的范围是 0~99,对于实时进程
  • 优先级的范围是 100~139,对于普通进程
  • 数值越小,优先级越高

实时调度策略

SCHED_FIFO、SCHED_RR、SCHED_DEADLINE 是实时进程的调度策略。

  • SCHED_FIFO
    相同优先级的进程,遵循先来先得
  • SCHED_RR
    轮流调度算法,采用时间片,相同优先级的任务当用完时间片会被放到队列尾部
  • SCHED_DEADLINE
    按照任务的 deadline 进行调度,总是选择其 deadline 距离当前时间点最近的那个任务

普通调度策略

SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE

  • SCHED_NORMAL
    普通的进程
  • SCHED_BATCH
    后台进程,几乎不需要和前端进行交互
  • SCHED_IDLE
    特别空闲的时候才跑的进程

调度策略的执行逻辑

task_struct 成员变量

\include\linux\sched.h

const struct sched_class *sched_class;

在这里插入图片描述
sched_class 指向封装了调度策略执行逻辑的类

sched_class 的几种实现:

  • stop_sched_class
    优先级最高的任务会使用这种策略,会中断所有其他线程,且不会被其他任务打断;
  • dl_sched_class
    对应上面的 deadline 调度策略
  • rt_sched_class
    对应 RR 算法或者 FIFO 算法的调度策略,具体调度策略由进程的 task_struct->policy 指定
  • fair_sched_class
    普通进程的调度策略
  • idle_sched_class
    空闲进程的调度策略

完全公平调度算法

在 Linux 里面,实现了一个基于 CFS 的调度算法。CFS 全称 Completely Fair Scheduling,叫完全公平调度。
记录下进程的运行时间,CFS 会为每一个进程安排一个虚拟运行时间 vruntime。

虚拟运行时间 vruntime += 实际运行时间 delta_exec * NICE_0_LOAD/ 权重

虚拟运行时间相同,权重高的(优先级高)实际运行时间多,权重低的(优先级低)实际运行时间少,

vruntime 少的,需要给它补上,所以会优先运行这样的进程。

调度队列与调度实体

以完全公平调度算法为例

CFS 需要一个数据结构来对 vruntime 进行排序,找出最小的那个

  • 这个数据结构在查询的时候,能够快速找到最小的,更新的时候也需要能够快速地调整排序
  • 能够平衡查询和更新速度的是树,在这里使用的是红黑树

调度实体

红黑树的的节点称为调度实体,包括了 vruntime
task_struct 成员变量
\include\linux\sched.h

struct sched_entity se;
struct sched_rt_entity rt;
struct sched_dl_entity dl;

在这里插入图片描述
实时调度实体 sched_rt_entity,Deadline 调度实体 sched_dl_entity,以及完全公平算法调度实体 sched_entity。

进程根据自己是实时的,还是普通的类型,通过这个成员变量,将自己挂在某一个数据结构里面,和其他的进程排序,等待被调度。如果这个进程是个普通进程,则通过 sched_entity,将自己挂在这棵红黑树上。

普通进程的调度实体

\include\linux\sched.h

struct sched_entity {
  struct load_weight    load;
  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;
......
};

在这里插入图片描述
红黑树
图片来自极客时间趣谈linux操作系统
所有可运行的进程通过不断地插入操作最终都存储在以时间为顺序的红黑树中,vruntime 最小的在树的左侧,vruntime 最多的在树的右侧。
CFS 调度策略会选择红黑树最左边的叶子节点作为下一个将获得 CPU 的任务。

进程队列

每个 CPU 都有自己的 struct rq 结构,其用于描述在此 CPU 上所运行的所有进程,其包括一个实时进程队列 rt_rq 和一个 CFS 运行队列 cfs_rq,这棵红黑树放在这里。

实时进程队列 rt_rq

\kernel\sched\sched.h


struct rq {
  /* runqueue lock: */
  raw_spinlock_t lock;
  unsigned int nr_running;
  unsigned long cpu_load[CPU_LOAD_IDX_MAX];
......
  struct load_weight load;
  unsigned long nr_load_updates;
  u64 nr_switches;


  struct cfs_rq cfs;
  struct rt_rq rt;
  struct dl_rq dl;
......
  struct task_struct *curr, *idle, *stop;
......
};
	

在这里插入图片描述

普通进程公平队列 cfs_rq

\kernel\sched\sched.h


/* CFS-related fields in a runqueue */
struct cfs_rq {
  struct load_weight load;
  unsigned int nr_running, h_nr_running;


  u64 exec_clock;
  u64 min_vruntime;
#ifndef CONFIG_64BIT
  u64 min_vruntime_copy;
#endif
  struct rb_root tasks_timeline;
  struct rb_node *rb_leftmost;


  struct sched_entity *curr, *next, *last, *skip;
......
};
	

在这里插入图片描述
rb_root 就是红黑树的根节点,rb_leftmost 指向的是最左面的节点

\include\linux\rbtree.h

struct rb_node {
	unsigned long  __rb_parent_color;
	struct rb_node *rb_right;
	struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
    /* The alignment might seem pointless, but allegedly CRIS needs it */

struct rb_root {
	struct rb_node *rb_node;
};
	

在这里插入图片描述

数据结构的关系

图片来自极客时间趣谈linux操作系统

调度类是如何工作的

调度类的定义

\kernel\sched\sched.h

struct sched_class {
	const struct sched_class *next;

	void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
	void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
	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);

	/*
	 * It is the responsibility of the pick_next_task() method that will
	 * return the next task to call put_prev_task() on the @prev task or
	 * something equivalent.
	 *
	 * May return RETRY_TASK when it finds a higher prio class has runnable
	 * tasks.
	 */
	struct task_struct * (*pick_next_task) (struct rq *rq,
						struct task_struct *prev,
						struct rq_flags *rf);
	void (*put_prev_task) (struct rq *rq, struct task_struct *p);

#ifdef CONFIG_SMP
	int  (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
	void (*migrate_task_rq)(struct task_struct *p);

	void (*task_woken) (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_fork) (struct task_struct *p);
	void (*task_dead) (struct task_struct *p);

	/*
	 * The switched_from() call is allowed to drop rq->lock, therefore we
	 * cannot assume the switched_from/switched_to pair is serliazed by
	 * rq->lock. They are however serialized by p->pi_lock.
	 */
	void (*switched_from) (struct rq *this_rq, struct task_struct *task);
	void (*switched_to) (struct rq *this_rq, struct task_struct *task);
	void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
			     int oldprio);

	unsigned int (*get_rr_interval) (struct rq *rq,
					 struct task_struct *task);

	void (*update_curr) (struct rq *rq);

#define TASK_SET_GROUP  0
#define TASK_MOVE_GROUP	1

#ifdef CONFIG_FAIR_GROUP_SCHED
	void (*task_change_group) (struct task_struct *p, int type);
#endif
};
	

在这里插入图片描述

调度类分为这几种

\kernel\sched\sched.h


extern const struct sched_class stop_sched_class;
extern const struct sched_class dl_sched_class;
extern const struct sched_class rt_sched_class;
extern const struct sched_class fair_sched_class;
extern const struct sched_class idle_sched_class;

在这里插入图片描述

取下一个任务 pick_next_task

\kernel\sched\sched.h

/*
 * Pick up the highest-prio task:
 */
static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
	const struct sched_class *class;
	struct task_struct *p;

	/*
	 * Optimization: we know that if all tasks are in the fair class we can
	 * call that function directly, but only if the @prev task wasn't of a
	 * higher scheduling class, because otherwise those loose the
	 * opportunity to pull in more work from other CPUs.
	 */
	if (likely((prev->sched_class == &idle_sched_class ||
		    prev->sched_class == &fair_sched_class) &&
		   rq->nr_running == rq->cfs.h_nr_running)) {

		p = fair_sched_class.pick_next_task(rq, prev, rf);
		if (unlikely(p == RETRY_TASK))
			goto again;

		/* Assumes fair_sched_class->next == idle_sched_class */
		if (unlikely(!p))
			p = idle_sched_class.pick_next_task(rq, prev, rf);

		return p;
	}

again:
	for_each_class(class) {
		p = class->pick_next_task(rq, prev, rf);
		if (p) {
			if (unlikely(p == RETRY_TASK))
				goto again;
			return p;
		}
	}

	/* The idle class should always have a runnable task: */
	BUG();
}
	

在这里插入图片描述
这里面有一个 for_each_class 循环,沿着上面调度类分类的顺序,依次调用每个调度类的方法。调度的时候是从优先级最高的调度类到优先级低的调度类,依次执行

对于每种调度类,有自己的实现,例如,CFS 就有 fair_sched_class。
\kernel\sched\fair.c

const struct sched_class fair_sched_class = {
	.next			= &idle_sched_class,
	.enqueue_task		= enqueue_task_fair,
	.dequeue_task		= dequeue_task_fair,
	.yield_task		= yield_task_fair,
	.yield_to_task		= yield_to_task_fair,

	.check_preempt_curr	= check_preempt_wakeup,

	.pick_next_task		= pick_next_task_fair,
	.put_prev_task		= put_prev_task_fair,

#ifdef CONFIG_SMP
	.select_task_rq		= select_task_rq_fair,
	.migrate_task_rq	= migrate_task_rq_fair,

	.rq_online		= rq_online_fair,
	.rq_offline		= rq_offline_fair,

	.task_dead		= task_dead_fair,
	.set_cpus_allowed	= set_cpus_allowed_common,
#endif

	.set_curr_task          = set_curr_task_fair,
	.task_tick		= task_tick_fair,
	.task_fork		= task_fork_fair,

	.prio_changed		= prio_changed_fair,
	.switched_from		= switched_from_fair,
	.switched_to		= switched_to_fair,

	.get_rr_interval	= get_rr_interval_fair,

	.update_curr		= update_curr_fair,

#ifdef CONFIG_FAIR_GROUP_SCHED
	.task_change_group	= task_change_group_fair,
#endif
};

在这里插入图片描述
同理 在 \kernel\sched\rt.c 有 const struct sched_class rt_sched_class

对于同样的 pick_next_task 选取下一个要运行的任务这个动作,不同的调度类有自己的实现。fair_sched_class 的实现是 pick_next_task_fair,rt_sched_class 的实现是 pick_next_task_rt。

  • pick_next_task_fair
    \kernel\sched\fair.c
	static struct task_struct *
pick_next_task_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
  • pick_next_task_rt
    \kernel\sched\fair.c
static struct task_struct *
pick_next_task_rt(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)

sched_class 定义的与调度有关的函数

  • enqueue_task 向就绪队列中添加一个进程,当某个进程进入可运行状态时,调用这个函数;
  • dequeue_task 将一个进程从就绪队列中删除;
  • pick_next_task 选择接下来要运行的进程
  • put_prev_task 用另一个进程代替当前运行的进程
  • set_curr_task 用于修改调度策略
  • task_tick 每次周期性时钟到的时候,这个函数被调用,可能触发调度。

总结

每个 CPU 上都有一个队列 rq,这个队列里面包含多个子队列,例如 rt_rq 和 cfs_rq,不同的队列有不同的实现方式,cfs_rq 就是用红黑树实现的。

某个 CPU 需要找下一个任务执行的时候,会按照优先级依次调用调度类,不同的调度类操作不同的队列。当然 rt_sched_class 先被调用,它会在 rt_rq 上找下一个任务,只有找不到的时候,才轮到 fair_sched_class 被调用,它会在 cfs_rq 上找下一个任务。这样保证了实时任务的优先级永远大于普通任务。

例子:
fair_sched_class 对于 pick_next_task 的实现 pick_next_task_fair
调用路径如下:pick_next_task_fair->pick_next_entity->__pick_first_entity。
\kernel\sched\fair.c

struct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq)
{
	struct rb_node *left = cfs_rq->rb_leftmost;

	if (!left)
		return NULL;

	return rb_entry(left, struct sched_entity, run_node);
}

在这里插入图片描述

一个 CPU 上有一个队列,CFS 的队列是一棵红黑树,树的每一个节点都是一个 sched_entity,每个 sched_entity 都属于一个 task_struct,task_struct 里面有指针指向这个进程属于哪个调度类

图片来自极客时间趣谈linux操作系统

参考资料:

趣谈Linux操作系统(极客时间)链接:
http://gk.link/a/10iXZ
欢迎大家来一起交流学习

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值