Linux源码阅读笔记03-调度器及CFS调度器

简介: Linux源码阅读笔记03-调度器及CFS调度器

调度器

  • 调度器:Linux内核中用来安排调度进程(一段程序的执行过程)执行的模块成为调度器,他可以切换进程状态。比如:执行、可中断睡眠、不可中断睡眠、退出、暂停等;
  • 调度器的主要职责:选择某些就绪的进程来运行、打断某些执行的进程让他们变为就绪态;
  • 调度目的:最大效率使用CPU资源

  • 主动进入阻塞状态:
  • 被动进去阻塞状态:

如果调度器支持就绪状态切换到执行状态,同时支持执行状态切换到就绪状态,则称为抢占式调度器。

调度类sched_class结构体

// 调度类的结构体
struct sched_class {
  const struct sched_class *next; // 系统中有多个调度类,按照优先级按照调度优先级直接排成链表
#ifdef CONFIG_UCLAMP_TASK
  int uclamp_enabled;
#endif
  // 将进程加入到执行队列当中,即将调度实体(进程)存放到红黑树当中,并且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的执行权限,实际上此函数执行先出队后入队,这种情况下直接将调度实体存放在红黑树的最右端
  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);
  // 选择下一个要运行的进程
  struct task_struct *(*pick_next_task)(struct rq *rq);
  // 将进程加入到运行队列中
  void (*put_prev_task)(struct rq *rq, struct task_struct *p);
  void (*set_next_task)(struct rq *rq, struct task_struct *p, bool first);
#ifdef CONFIG_SMP
  int (*balance)(struct rq *rq, struct task_struct *prev, struct rq_flags *rf);
  // 选择合适的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, int new_cpu);
  // 唤醒进程
  void (*task_woken)(struct rq *this_rq, struct task_struct *task);
  // 修改进程在CPU的亲和力
  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 (*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
};

调度器分类

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; // 空闲调度类
  • 优先级(高到低):停机调度类-限期调度类-实时调度类-公平调度类-空闲调度类;
  • 停机调度类:停止进程,优先级最高,可以抢占其他进程;停机进程是优先级最高的进程,可以抢占其他进程;
  • 限期调度类: 最早使用的优先算法,使用红黑树把进程按照绝对截止的期限从小到大排序,每次调度会选择截止期限最小的进程;
  • 实时调度类:为每一个调度优先级维护一个队列;
  • 公平调度类:使用完全公平调度算法。完全公平调度算法引入虚拟运行时间:虚拟运行时间=实际运行实现*nice0对应的权重/进程的权重;
  • 空闲调度类:每个CPU上有一个空闲的线程,即0号线程。空闲调度类优先级最低,仅仅当没有其他进程可以调度的时候才会调度空闲线程。

进程优先级

// Linux 内核优先级
#define MAX_USER_RT_PRIO  100
#define MAX_RT_PRIO   MAX_USER_RT_PRIO
#define MAX_PRIO    (MAX_RT_PRIO + NICE_WIDTH)
#define DEFAULT_PRIO    (MAX_RT_PRIO + NICE_WIDTH / 2)
  • 实时进程(Real-Time-Process):优先级高,需要立刻被执行的进程
  • 普通进程(Normal-Process):优先级低,更长执行时间的进程

进程优先级是一个0-139的整数来表示。数字越小,优先级越高,其中优先级0-99留给实时进程,100-139留给普通进程。

内核调度策略

Linux内核提供一些调度策略让用户应用程序选择调度器。Linux内核调度策略源码如下:

  • SCHED_NORMAL:普通进程调度策略,使得ask选择CFS调度器来调度运行;
  • SCHED_FIFO:实时进程调度策略,先进先出调度没有时间片,没有更高级优先级的状态下,只有等待主动让出CPU;
  • SCHED_RR:实时进程调度策略,时间片轮转,进程使用完时间片之后加入优先级对应运行队列中的尾部,把CPU让给同等优先级的其他进程;
  • SCHED_BATCH:普通进程调度策略,批量处理,使task选择CFS调度器来调度运行;
  • SCHED_IDLE:普通进程调度策略,使task以最低优先级选择CFS调度器来运行;
  • SCHED_DEADLINE:限期进程调度策略,使task选择Deadline调度器来运行。

备注:其中 Stop 调度器和 IDLE-task 调度器,仅使用于内核,用户没有办法进行选择。

CFS调度器

完全公平调度算法体现在对每个进程都是公平的,让每个进程都运行一段相同的的时间片,这就是基于时间片轮询调度算法。CFS定义一种新模型,给cfs_rq(CFS的run queue)中的每个进程都设置一个虚拟时钟virtual runtime。古国一个进程得以执行,随着时间的不断增长,他的vruntime也会不断增大,没有得到执行的进程vruntime保持不变。

进程描述符task_struct结构体中,有几个成员与调度相关,具体成员prio、normal_prio、static_prio、rt_priority等。

实际运行时间

CFS(Completely Fair Scheduler,完全公平调度器)。在实际当中必然会有进程优先级高或者进程优先级低,CFS引入权重代表进程的优先级,各个进程按照权重比例分配CPU时间。

假设有两个进程X和Y,X权重为1024,Y权重为2048。

X获得CPU的时间比为:

1024 1024 + 2048 = 33 % \frac{1024}{1024+2048}=33\%1024+20481024=33%

Y获得CPU的时间比例为:

2048 1024 + 2048 = 66 % \frac{2048}{1024+2048}=66\%1024+20482048=66%

在引入权重之后分配给进程的时间计算公式如下:

实际运行时间 = 调度曲线 ∗ 进程权重 所有进程权重和 实际运行时间=\frac{调度曲线*进程权重}{所有进程权重和}实际运行时间=所有进程权重和调度曲线∗进程权重

虚拟运行时间

虚拟运行时间 = 实际运行时间 ∗ N I C E _ 0 _ L O A D 进程权重 = 调度周期 ∗ 进程权重 所有进程权重 ∗ N I C E _ 0 _ L O A D 进程权重 = 调度周期 ∗ 1024 所有进程权重 虚拟运行时间=\frac{实际运行时间*NICE\_0\_LOAD}{进程权重}=\frac{调度周期*进程权重}{所有进程权重}*\frac{NICE\_0\_LOAD}{进程权重}=\frac{调度周期*1024}{所有进程权重}虚拟运行时间=进程权重实际运行时间∗NICE_0_LOAD=所有进程权重调度周期∗进程权重∗进程权重NICE_0_LOAD=所有进程权重调度周期∗1024

在一个调度周期里,所有进程的虚拟运行时间是相同的,所以在进程调度时,只要找到虚拟运行时间最小的进程调度即可。

CFS调度器类

  • enqueue_task:当任务进入可运行状态时,此函数将调度实体存入红黑树,完成入队操作。
  • dequeue_task:当任务退出可运行状态时,此函数将调度实体从红黑树中移除,完成出队操作。

这两个函数接收三个参数运行队列 任务实体 标志,rq是一个抽象出来的运行队列结构体。这个结构体中的包含了完全公平调度器就绪队列 实时调度器就绪队列 限时调度器就绪队列

CFS调度器就绪队列内核源码

/* CFS-related fields in a runqueue */
struct cfs_rq {
  struct load_weight  load;
  unsigned long   runnable_weight;
  unsigned int    nr_running;
  unsigned int    h_nr_running;      /* SCHED_{NORMAL,BATCH,IDLE} */
  unsigned int    idle_h_nr_running; /* SCHED_IDLE */
  u64     exec_clock;
  u64     min_vruntime;
#ifndef CONFIG_64BIT
  u64     min_vruntime_copy;
#endif
  struct rb_root_cached tasks_timeline;
  /*
   * 'curr' points to currently running entity on this cfs_rq.
   * It is set to NULL otherwise (i.e when none are currently running).
   */
  // 可被内核调度的实体
  struct sched_entity *curr;
  struct sched_entity *next;
  struct sched_entity *last;
  struct sched_entity *skip;
#ifdef  CONFIG_SCHED_DEBUG
  unsigned int    nr_spread_over;
#endif
#ifdef CONFIG_SMP
  /*
   * CFS load tracking
   */
  struct sched_avg  avg;
#ifndef CONFIG_64BIT
  u64     load_last_update_time_copy;
#endif
  struct {
    raw_spinlock_t  lock ____cacheline_aligned;
    int   nr;
    unsigned long load_avg;
    unsigned long util_avg;
    unsigned long runnable_sum;
  } removed;
#ifdef CONFIG_FAIR_GROUP_SCHED
  unsigned long   tg_load_avg_contrib;
  long      propagate;
  long      prop_runnable_sum;
  /*
   *   h_load = weight * f(tg)
   *
   * Where f(tg) is the recursive weight fraction assigned to
   * this group.
   */
  unsigned long   h_load;
  u64     last_h_load_update;
  struct sched_entity *h_load_next;
#endif /* CONFIG_FAIR_GROUP_SCHED */
#endif /* CONFIG_SMP */
#ifdef CONFIG_FAIR_GROUP_SCHED
  struct rq   *rq;  /* CPU runqueue to which this cfs_rq is attached */
  /*
   * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
   * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
   * (like users, containers etc.)
   *
   * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a CPU.
   * This list is used during load balance.
   */
  int     on_list;
  struct list_head  leaf_cfs_rq_list;
  struct task_group *tg;  /* group that "owns" this runqueue */
#ifdef CONFIG_CFS_BANDWIDTH
  int     runtime_enabled;
  s64     runtime_remaining;
  u64     throttled_clock;
  u64     throttled_clock_task;
  u64     throttled_clock_task_time;
  int     throttled;
  int     throttle_count;
  struct list_head  throttled_list;
#endif /* CONFIG_CFS_BANDWIDTH */
#endif /* CONFIG_FAIR_GROUP_SCHED */
};

cfs_rq:跟踪就绪队列信息,管理就绪态调度实体,维护一棵按照虚拟时间排序的红黑树。tasks_timeline->rb_root指向红黑树的根,tasks_timeline->rb_leftmost指向红黑树最左边的调度实体,即虚拟时间最小的调度实体。


相关文章
|
1月前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
73 4
|
1月前
|
缓存 算法 Linux
深入理解Linux内核调度器:公平性与性能的平衡####
真知灼见 本文将带你深入了解Linux操作系统的核心组件之一——完全公平调度器(CFS),通过剖析其设计原理、工作机制以及在实际系统中的应用效果,揭示它是如何在众多进程间实现资源分配的公平性与高效性的。不同于传统的摘要概述,本文旨在通过直观且富有洞察力的视角,让读者仿佛亲身体验到CFS在复杂系统环境中游刃有余地进行任务调度的过程。 ####
54 6
|
2月前
|
Ubuntu Linux Python
Tkinter错误笔记(一):tkinter.Button在linux下出现乱码
在Linux系统中,使用Tkinter库时可能会遇到中文显示乱码的问题,这通常是由于字体支持问题导致的,可以通过更换支持中文的字体来解决。
168 0
Tkinter错误笔记(一):tkinter.Button在linux下出现乱码
|
26天前
|
负载均衡 算法 Linux
深入探索Linux内核调度器:公平与效率的平衡####
本文通过剖析Linux内核调度器的工作机制,揭示了其在多任务处理环境中如何实现时间片轮转、优先级调整及完全公平调度算法(CFS),以达到既公平又高效地分配CPU资源的目标。通过对比FIFO和RR等传统调度策略,本文展示了Linux调度器如何在复杂的计算场景下优化性能,为系统设计师和开发者提供了宝贵的设计思路。 ####
40 6
|
28天前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
1月前
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
50 6
|
1月前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
1月前
|
人工智能 算法 大数据
Linux内核中的调度算法演变:从O(1)到CFS的优化之旅###
本文深入探讨了Linux操作系统内核中进程调度算法的发展历程,聚焦于O(1)调度器向完全公平调度器(CFS)的转变。不同于传统摘要对研究背景、方法、结果和结论的概述,本文创新性地采用“技术演进时间线”的形式,简明扼要地勾勒出这一转变背后的关键技术里程碑,旨在为读者提供一个清晰的历史脉络,引领其深入了解Linux调度机制的革新之路。 ###
|
27天前
|
算法 前端开发 Linux
深入理解Linux内核调度器:CFS与实时性的平衡####
本文旨在探讨Linux操作系统的核心组件之一——完全公平调度器(CFS)的工作原理,分析其在多任务处理环境中如何实现进程间的公平调度,并进一步讨论Linux对于实时性需求的支持策略。不同于传统摘要仅概述内容要点,本部分将简要预览CFS的设计哲学、核心算法以及它是如何通过红黑树数据结构来维护进程执行顺序,同时触及Linux内核为满足不同应用场景下的实时性要求而做出的权衡与优化。 ####
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
116 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库