Linux的进程调度
进程调度算法用来解决以何种次序对各就绪进程进行处理机的分配以及按照何种时间比例让进程占用处理机。
一、完全公平调度算法简称CFS
Linux为了保证交互式应用和桌面系统的性能,所以对进程的响应做出了优化(缩短响应时间),更倾向于优先调度I/O消耗型进程。
二、进程优先级
调度算法中最基本的一类就是基于优先级的调度。
在Linux中采用了两种不同的优先级范围。第一种是:nice值,nice值越大说明优先级越低。在Linux中nice值代表的是时间片的比例。
第二种是实时优先级,任何实时进程的优先级都高于普通进程的优先级。就是说实时优先级和nice优先级是两个不同的范畴。
三、时间片
Linux是将使用比划分给进程。这样一来进程所获得的处理器时间其实是和系统负载密切相关的。这个比例进一步还会受到nice值的影响。,nice作为权重将调整进程的时间使用比。高nice值低优先级进程将被赋予低权重。
在CFS调度器,进程抢占时机取决于新的可运行程序消耗了多少处理器使用比。如果消耗的使用比比当前进程小。则新进程抢占。(我想应该指的是你自己进程的百分比,比如给你分配30%的使用比,我消耗了给我分配的1/3.而另一个进程分配70%的使用比,消耗了这70的1/2..这是可以抢占的。)
四、调度算法
Linux调度器是以模块的方法提供的,这样做的好处就是可以让不同进程选择自己相应的调度算法,这种模块化结构被称为调度器类,它允许多种不同的可动态添加的调度算法并存,调度自己范畴的进程。每个调度器都有自己的优先级。基础的调度器代码定义在sched.c中他会按照优先级遍历调度类。拥有一个可执行进程的最高优先级的调度器类胜出,去选择下面执行的那一个程序。
CFS是一个针对普通进程的调度器类。
五、Unix系统中的进程调度以及Linux的CFS
现代进程调度器有两个通用的概念:进程优先级和时间片。时间片是指:进程运行多长时间进程一旦启动就会有一个默认的时间片。具有更高优先级的进程将运行的跟频繁。而且也会被赋予更多的时间片。(前面说过Linux采用两种优先级范围第一种是nice值,nice大的进程优先级低,nice值小的进程优先级高。调度算法是CFS,归他管。)
CFS采用的方法是对时间片分配方式进行根本性的重新设计:完全摒弃时间片而是分配给进程一个处理器使用比重,通过这种方式CFS确保了进程调度中能有恒定的公平性,而将切换频率置于不断变动中。
进程所占处理器时间比例,受nice影响。而nice在Linux中被作为权重,权重大的多给你运行比。
六、Linux调度的实现
时间记账:调度器实体作为一个名为se的成员变量,嵌入在任务描述符内。
虚拟实时:vruntime变量存放进程的虚拟运行时间,是以ns为 单位的。update_curr函数(计算当前进程的执行时间并且将其存放在变量中,然后将运行时间传递给__update_curr函数由后者根据当前可运行程序总数对运行时间进行加权计算。最终将上述权重值与当前进程的vruntime相加)是由系统定时器周期性决定的。根据据这种方式vruntime可以准确的测量进程运行了多长时间。linuxCFS使用红黑书来组织可运行进程队列并利用其迅速找到最小vruntime值的进程。
七、调度器入口
schedule()会找到一个最高优先级的调度类----他这个类下有可运行队列。
八、睡眠和唤醒
DEFINE_WAIT(&wait); //申请一个wait项
add_wait_queue(q, &wait); //q表示你想要休眠到的队列头
wait_event() //等待事件
wake_up() //唤醒队列
九、抢占和上下文切换
总结:
Linux两个调度器:主调度器和周期调度器
主调度器和周期调度器:
多种方法可以调用调度器比如:进程直接放弃执行调用调度函数,或者休眠。还有就是通过周期性的频率运行不时的检测是有必要的。
Linux的6种调度策略(所谓策略就是程序在何时让什么进程运行):
字段 | 描述 | 所在调度器类 |
---|---|---|
SCHED_NORMAL | (也叫SCHED_OTHER)用于普通进程,通过CFS调度器实现。SCHED_BATCH用于非交互的处理器消耗型进程。SCHED_IDLE是在系统负载很低时使用 | CFS |
SCHED_BATCH | SCHED_NORMAL普通进程策略的分化版本。采用分时策略,根据动态优先级(可用nice()API设置),分配CPU运算资源。注意:这类进程比上述两类实时进程优先级低,换言之,在有实时进程存在时,实时进程优先调度。但针对吞吐量优化, 除了不能抢占外与常规任务一样,允许任务运行更长时间,更好地使用高速缓存,适合于成批处理的工作 | CFS |
SCHED_IDLE | 优先级最低,在系统空闲时才跑这类进程(如利用闲散计算机资源跑地外文明搜索,蛋白质结构分析等任务,是此调度策略的适用者) | CFS-IDLE |
SCHED_FIFO | 先入先出调度算法(实时调度策略),相同优先级的任务先到先服务,高优先级的任务可以抢占低优先级的任务 | RT |
SCHED_RR | 轮流调度算法(实时调度策略),后者提供 Roound-Robin 语义,采用时间片,相同优先级的任务当用完时间片会被放到队列尾部,以保证公平性,同样,高优先级的任务可以抢占低优先级的任务。不同要求的实时任务可以根据需要用sched_setscheduler() API设置策略 | RT |
SCHED_DEADLINE | 新支持的实时进程调度策略,针对突发型计算,且对延迟和完成时间高度敏感的任务适用。基于Earliest Deadline First (EDF) 调度算法 | DL |
idle的运行时机
idle 进程优先级为MAX_PRIO,即最低优先级。
早先版本中,idle是参与调度的,所以将其优先级设为最低,当没有其他进程可以运行时,才会调度执行 idle
而目前的版本中idle并不在运行队列中参与调度,而是在cpu全局运行队列rq中含idle指针,指向idle进程, 在调度器发现运行队列为空的时候运行, 调入运行
Linux5种调度器类:
调度器类 | 描述 | 对应调度策略 |
---|---|---|
stop_sched_class | 优先级最高的线程,会中断所有其他线程,且不会被其他任务打断 作用 1.发生在cpu_stop_cpu_callback 进行cpu之间任务migration 2.HOTPLUG_CPU的情况下关闭任务 |
无, 不需要调度普通进程 |
dl_sched_class | 采用EDF最早截至时间优先算法调度实时进程 | SCHED_DEADLINE |
rt_sched_class | 采用提供 Roound-Robin算法或者FIFO算法调度实时进程 具体调度策略由进程的task_struct->policy指定 |
SCHED_FIFO, SCHED_RR |
fair_sched_clas | 采用CFS算法调度普通的非实时进程 | SCHED_NORMAL, SCHED_BATCH |
idle_sched_class | 采用CFS算法调度idle进程, 每个cup的第一个pid=0线程:swapper,是一个静态线程。调度类属于:idel_sched_class,所以在ps里面是看不到的。一般运行在开机过程和cpu异常的时候做dump | SCHED_IDLE |
其所属进程的优先级顺序为
stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
Linux3个调度实体:
调度实体 | 名称 | 描述 | 对应调度器类 |
---|---|---|---|
sched_dl_entity | DEADLINE调度实体 | 采用EDF算法调度的实时调度实体 | dl_sched_class |
sched_rt_entity | RT调度实体 | 采用Roound-Robin或者FIFO算法调度的实时调度实体 | rt_sched_class |
sched_entity | CFS调度实体 | 采用CFS算法调度的普通非实时进程的调度实体 | fair_sched_class |
总表:
调度器类 | 调度策略 | 调度策略对应的调度算法 | 调度实体 | 调度实体对应的调度对象 |
---|---|---|---|---|
stop_sched_class | 无 | 无 | 无 | 特殊情况, 发生在cpu_stop_cpu_callback 进行cpu之间任务迁移migration或者HOTPLUG_CPU的情况下关闭任务 |
dl_sched_class | SCHED_DEADLINE | Earliest-Deadline-First最早截至时间有限算法 | sched_dl_entity | 采用DEF最早截至时间有限算法调度实时进程 |
rt_sched_class | SCHED_RR SCHED_FIFO |
Roound-Robin时间片轮转算法 FIFO先进先出算法 |
sched_rt_entity | 采用Roound-Robin或者FIFO算法调度的实时调度实体 |
fair_sched_class | SCHED_NORMAL SCHED_BATCH |
CFS完全公平懂调度算法 | sched_entity | 采用CFS算法普通非实时进程 |
idle_sched_class | SCHED_IDLE | 无 | 无 | 特殊进程, 用于cpu空闲时调度空闲进程idle |
Linux的task_struct结构体:
进程调度字段
static_prio用于保存静态优先级(默认值是从父进程继承而来),可以通过nice系统调用来进行修改。可以和nice值相互转化。注意在用户空间使用nice修改。在内核里使用优先级。
优先级的范围:
Nice值为0时对应的优先级为120.
Linux3.0.86内核Sched.c里的宏定义
#define NICE_TO_PRIO(nice) (MAX_RT_PRIO + (nice) + 20)
#define PRIO_TO_NICE(prio) ((prio) - MAX_RT_PRIO - 20)
#define TASK_NICE(p) PRIO_TO_NICE((p)->static_prio)
rt_priority用于保存实时优先级。
normal_prio的值取决于静态优先级和调度策略(在普通进程与静态优先级一样)。
prio用于保存动态优先级(通常我们所说的优先级就是动态优先级,因为这个值是在静态优先级的基础上进行的加减的,系统会看这个值,计算与静态优先级有关,受bouns影响这个值是和进程休眠时间有关的一个值)
nice值还会不断对old优先级进行更改,当然也可以设置nice的值,nice值给负值必须要用root
权重与运行时间成正比,虚拟运行时间的增长速度与权重成正比,权重高的增长就慢就越容易执行。所有进程的运行时间依赖于他自己和其他进程的nice值的差值(因为nice可以转化为权重)。nice值并不是优先级,但是它可以影响到优先级,nice值在 CFS中作为进程获得的处理器运行比的权重(体现在有一个数组对应了每个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,
};
(系统设定一个period值(它表示一段时间),每个进程对应一个ideal_runtime值(称为理想欲运行时间),每个进程的ideal_runtime值的设定方式:所有可运行进程的ideal_runtime值的和等于period,每个进程的ideal_runtime值的大小与它的权重weight成正比。该模型规定:每个进程每次获得CPU使用权,最多执行它对应的ideal_runtime这样长的时间。)
policy表示进程的调度策略,目前主要有以下五种:
SCHED_NORMAL用于普通进程,通过CFS调度器实现。SCHED_BATCH用于非交互的处理器消耗型进程。SCHED_IDLE是在系统负载很低时使用。
SCHED_FIFO(先入先出调度算法)和SCHED_RR(轮流调度算法)都是实时调度策略。
sched_class结构体指针表示调度类链表,
struct sched_class {
/* 系统中多个调度类, 按照其调度的优先级排成一个链表
下一优先级的调度类
* 调度类优先级顺序: stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
*/
const struct sched_class *next;
/* 将进程加入到运行队列中,即将调度实体(进程)放入红黑树中,并对 nr_running 变量加1 */
void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
/* 从运行队列中删除进程,并对 nr_running 变量中减1 */
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
/* 放弃CPU,在 compat_yield sysctl 关闭的情况下,该函数实际上执行先出队后入队;在这种情况下,它将调度实体放在红黑树的最右端 */
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);
/* 将进程放回运行队列 */
void (*put_prev_task) (struct rq *rq, struct task_struct *p);
#ifdef CONFIG_SMP
/* 为进程选择一个合适的CPU */
int (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
/* 迁移任务到另一个CPU */
void (*migrate_task_rq)(struct task_struct *p);
/* 用于进程唤醒 */
void (*task_waking) (struct task_struct *task);
void (*task_woken) (struct rq *this_rq, struct task_struct *task);
/* 修改进程的CPU亲和力(affinity) */
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);
/* 该函数通常调用自 time tick 函数;它可能引起进程切换。这将驱动运行时(running)抢占 */
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);
#ifdef CONFIG_FAIR_GROUP_SCHED
void (*task_move_group) (struct task_struct *p);
#endif
};
next成员指向下个优先级的调度类。
stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
se和rt都是调用实体,一个用于普通进程,一个用于实时进程,每个进程都有其中之一的实体。
cpus_allowed用于控制进程可以在哪里处理器上运行。
Linux CPU就绪队列struct rq
struct cfs_rq cfs;/*为cfs fair class 的rq就绪队列 */
struct rt_rq rt;/* 为rt time class 的rq就绪队列 */
cfs_rq结构体// 每个CPU有一个CFS_RQ结构体里边有调度器实体sched_entity链表
Linux调度器实体sched_entity结构体
作为se成员被嵌入在task_strut结构体中,有一个虚拟运行时间成员vruntime
每个时钟周期内一个进程的虚拟运行时间是通过下面的方法计算的:一次调度间隔的虚拟运行时间=实际运行时间*(NICE_0_LOAD/权重)
在新创建一个进程或者唤醒一个进程的话就会调用更新虚拟运行时间的函数