Linux操作系统之进程如何调度?

Posted Dufre.WC

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux操作系统之进程如何调度?相关的知识,希望对你有一定的参考价值。

前言

操作系统对我来说很抽象的,学了几年,工作中天天打交道,却不懂它。偶然听了极客时间的一门课(这里就不打广告了),很有启发。其实操作系统就好比一个公司:

  • 进程管理:可以理解为公司的项目管理
  • 内存管理:可以理解为公共资源的管理,比如会议室
  • 文件系统:可以理解为项目开发过程中文档的管理
  • 输入输出系统:可以理解为售前售后体系

公司有很多项目,但人手是有限的,有的项目时间很紧,有的项目是VIP客户,应该先排;有的项目可以缓缓,但也不能让客户等太久,所以作为公司老大,你应该如何分配任务呢?
对操作系统来说,CPU数量也是有限的,但是进程的数量远远超过CPU数目,这就需要进程的调度,有效的分配CPU的时间,既要保证进程的最快响应,也要保证进程之间的公平。

调度策略与调度类

Linux进程可以分为两种:

  • 实时进程:需要尽快返回结果
  • 普通进程:优先级没有实时进程那么高
    对两种进程,我们的调度策略也是不同的,在task_struct中,有一个成员变量,叫调度策略
unsigned int policy;

有下面几个定义:

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

实时调度策略

  • SCHED_FIFO:先来先服务,
    • 高优先级的进程可以抢占低优先级的进程
    • 相同优先级的进程,遵循先来先得
  • SCHED_RR:轮流调度算法
    • 高优先级的进程可以抢占低优先级的进程
    • 相同优先级的任务当用完时间片会被放到队列尾部,以保证公平
  • SCHED_DEADLINE:按照任务的deadline进行调度
    • 当产生一个调度点的时候,选择deadline距离当前时间点最近的任务

普通调度策略

  • SCHED_NORMAL
    • 普通的进程
  • SCHED_BATCH
    • 后台进程
  • SCHED_IDLE
    • 特别空闲才跑的进程

CFS(完全公平调度算法)

CFS(Completely Fair Scheduling),它引入了权重的思想。想象一下,假设你是进程调度器,在某个时间点,你会选择哪一个进程运行?

  • 优先级高的进程理应分到更多的时间
  • 优先级低的进程也不可能一点时间也分不到
    你肯定会有一个标准,那在CFS算法里,这个标准叫做vruntime,也就是虚拟运行时间,CFS调度算法总是会选择vruntime小的进程,那这个vruntime是怎么计算的呢?看看代码:

/*
 * Update the current task's runtime statistics.
 */
static void update_curr(struct cfs_rq *cfs_rq)

  struct sched_entity *curr = cfs_rq->curr;
  u64 now = rq_clock_task(rq_of(cfs_rq));
  u64 delta_exec;
......
  //这次运行的实际时间 = 当前时间 - 时间片开始的时间
  delta_exec = now - curr->exec_start;
......
  curr->exec_start = now;
......
  curr->sum_exec_runtime += delta_exec;
......
  curr->vruntime += calc_delta_fair(delta_exec, curr);
  update_min_vruntime(cfs_rq);
......



/*
 * delta /= w
 */
static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)

  if (unlikely(se->load.weight != NICE_0_LOAD))
    //这次运行的实际时间 * NICE_0_LOAD/权重
    delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
  return delta;

实际运行时间到虚拟运行时间的转化方法如下
虚拟运行时间 += 实际运行时间(delta_exec) * NICE_0_LOAD / 权重
就是说,同样的运行时间,权重高的虚拟运行时间比权重低的要小,所以权重高的进程获得的实际运行时间自然就多了。但是随着实际运行时间的增长,虚拟运行时间也会跟着增长,只不过由于权重大的原因,增长的会慢一点。从这个公式可以看出:

  • 一样的虚拟运行时间vruntime:权重高的实际运行时间也多
  • 一样的实际运行时间delta_exec:权重高的虚拟运行时间少
    打个比方,公司有两个项目,A和B,A能赚200万,B能赚100万,那作为公司老大,我会让我的手下们在工作中尽量做到花2/3的时间在项目A上,花1/3的时间在项目B上。

调度队列与调度实体

上面说到CFS算法总是会挑选vruntime最小的进程,所以它需要一种数据结构来告诉它,谁是最小的?而且这个vruntime也是经常在变的,所以为了平衡查询和更新速度,这里使用的是红黑树
对于普通进程的调度实体如下:


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;
......
;

下图是一个例子,所有可运行的进程通过不断地插入操作最终都存储在以时间为顺序的红黑树种,vruntime最小的在树的左侧,vruntime最多的在树的右侧。CFS调度策略会选择红黑树最左边的叶子节点作为下一个将获得CPU的任务。

每个CPU都有自己的struct rq结构,其用于描述在此CPU上所运行的所有进程,它包括:

  • 实时进程队列rt_rq
  • CFS运行队列cfs_rq
    在调度时,调度器首先会先去实时进程队列找到是否有实时进程需要运行,如果没有才会去CFS运行队列找是否有进程需要运行。

以上是关于Linux操作系统之进程如何调度?的主要内容,如果未能解决你的问题,请参考以下文章

Linux操作系统之进程如何调度?

Linux(内核剖析):09---进程调度之Linux调度的实现(struct sched_entityschedule())

Linux环境之进程调度算法

Linux(内核剖析):12---进程调度之与调度相关的系统调用

linux内核情景分析之强制性调度

Linux 进程管理之进程调度与切换