前言
为了解决资源少需求多的矛盾,“调度”应运而生。在生活中我们我们经常遇到调度的场景,比如物流公司根据客户需求的紧迫程度对车辆的调度,餐厅老板为了同时留住更多顾客对上餐顺序的调度等等。计算机领域也不例外,当待处理的任务个数远超过有限的计算资源(cpu个数)时,就需要调度器来同时满足多个任务的需求。内核调度器的目标只有一个,就是从众多任务中挑选出“最合适”的任务在cpu上运行。针对不同需求,各种调度器的的设计目标也会有所侧重,比如侧重于吞吐率、实时性、公平性等等。调度器是操作系统的一部分,如果说操作系统是将计算机的硬件资源进行抽象管理,那么调度器就是负责管理计算机的计算资源cpu。
内核调度器内部模块
假如我们从万米高空俯视Linux5.10调度器,它的内部结构如下所示:
调度类
一个系统中往往同时存在各种不同特性的任务,比如一些批处理任务运算量大但对实时性要求不高,再比如一些人机交互的任务要求响应及时,对实时性要求就比较高,所以不同的类型的任务就需要根据不同的调度策略做调度,不同的策略就需要不同的调度器算法来实现,Linux在2.6版本中引入了“调度类(Scheduling Class)”的概念,将调度器模块化,即抽象出各种调度器的通用行为,将调度策略封装到一个调度类 struct sched_class 的实例中,使得调度器核心代码不用再关心调度策略的具体细节,同时也方便了内核后续扩展增加其他调度器。关系如下:
调度类抽象出来的通用接口如下:
struct sched_class {
void (*enqueue_task)(struct rq *rq, struct task_struct *p, int flags);
//把任务p插入rq中的该调度器对应的就绪队列
void (*dequeue_task)(struct rq *rq, struct task_struct *p, int flags);
//把任务p移出就绪队列
void (*yield_task)(struct rq *rq);
//当前任务current主动释放CPU,但是其状态依然是runnable
bool (*yield_to_task)(struct rq *rq, struct task_struct *p);
//当前任务current主动释放CPU给任务p,但是其状态依然是runnable
void (*check_preempt_curr)(struct rq *rq, struct task_struct *p, int flags);
//检查进入runnable状态的p是否应该抢占该rq中的正在运行的current任务,如果是则做对应的处理。
//比如刚唤醒一个任务时,为该任务选择了合适的rq,会检测该任务是否应该抢占该rq上的current任务,
//如果应该抢占,那么就设置该current任务的TIF_NEED_RESCHED标志。
struct task_struct *(*pick_next_task)(struct rq *rq);
//从rq中选择出下一个要运行的任务
void (*put_prev_task)(struct rq *rq, struct task_struct *p);
void (*set_next_task)(struct rq *rq, struct tas