Linux内核设计与实现——第4章:进程调度

  • 多任务
    • 抢占式和非抢占式
  • Linux的进程调度
    • 2.5前的O(n)调度
    • 2.5的O(1)调度
    • 2.6的 完全公平调度算法(CFS)
  • 策略
    • 进程分类:I/O 和 CPU消耗型进程区别
    • 进程优先级:nice和priority
    • 实时进程和普通进程
  • Linux调度类
    • 调度器:主调度器+周期调度器
    • 调度策略:实时(FIFO + RR)/ 普通(NORMAL)
  • CFS 完全公平调度算法
    • 分配规则
    • vruntime体现公平性
    • 数据结构
  • 抢占和上下文切换
    • 上下文切换
    • 用户抢占和内核抢占的区别

4.1 多任务

多任务操作系统:多进程并发执行,可以划分为两类

  • 非抢占式多任务(cooperative multitasking):由调度程序来决定何时停止一个进程的运行,通常与时间片(timeslice)搭配使用
  • 抢占式多任务(preemptive multitasking):进程一直执行,直到它完成或主动让步,然后处理器切换到另一个进程。绝大多数操作系统基本不用这个

4.2 Linux的进程调度

(1)历史演变

  • 2.5之前:复杂度为O(n)的调度算法,从1991年Linux的第1版到后来的2.4,Linux调度程序都很简陋
  • 2.5开始:引入O(1)调度程序,特点是在拥有数以十计的多CPU环境下,性能完美。缺点是对于交互程序体验不佳
  • 2.6开始,Linux的“完全公平调度算法”(简称CFS)代替了O(1)算法,该算法基于“反转楼梯最后期限调度算法”理论(Rotating Staircase Deadline scheduler)

(2)调度算法:

  • O(n)算法
    • pick next算法,遍历runqueue中所有进程,选出优先级最高的进程调用
    • 缺点很明显,进程越来越多时,处理器消耗大量时间
  • O(1)算法
    • 进程优先级范围是0到139,共140个。因此该算法为每个优先级都设置了一个runqueue,即包含140个可运行状态的进程链表
    • 用一个bitmap记录这140个进程链表,因此需要5个32位int来表示,每个进程占一个bit。
    • 初始bitmap中的所有位都被置0,当某个优先级的进程处于可运行状态时,该优先级所对应的位就被置1
    • 选取过程可以理解为:遍历bitmap,找到对应位为1 且 优先级最高的那个
  • CFS算法
    • 楼梯调度算法 staircase scheduler
    • RSDL旋转楼梯(Rotating Staircase Deadline Schelduler)
    • CFS 完全公平调度器
      • 该算法吸取了上面两个的思想,最终被内核采纳
      • 详细见下面4.4
  • BFS调度器
    • 又称“脑残调度器”,没啥用,见Reference

(3)Reference


4.3 策略

(1)进程的分类

  • 第一种分类方式:对于这种进程,调度器应该尽量降低它们的调度频率,延长运行时间
类型描述示例评价
I/O消耗型,更注重响应时间进程大多数时间用来提交或等待I/O请求数据库服务器, 文本编辑器真正运行时间短,多数时间用于阻塞
CPU消耗型,更注重吞吐量大多数时间用于执行代码上深度学习训练模型对于这种进程,调度器应该尽量降低它们的调度频率,延长运行时间
  • 第二种分类方式
类型描述示例
交互式进程(interactive process)此类进程经常与用户进行交互, 因此需要花费很多时间等待键盘和鼠标操作. 当接受了用户的输入后, 进程必须很快被唤醒, 否则用户会感觉系统反应迟钝shell, 文本编辑程序和图形应用程序
批处理进程(batch process)此类进程不必与用户交互, 因此经常在后台运行. 因为这样的进程不必很快相应, 因此常受到调度程序的怠慢程序语言的编译程序, 数据库搜索引擎以及科学计算
实时进程(real-time process)这些进程由很强的调度需要, 这样的进程绝不会被低优先级的进程阻塞. 并且他们的响应时间要尽可能的短视频音频应用程序, 机器人控制程序以及从物理传感器上收集数据的程序

(2)进程优先级的概念

  • 传统操作系统:优先级高的进程先运行,低的后运行,相同优先级的进程按轮询挨个调度
  • Linux:优先级决定了该进程在所有可用进程中的时间占比,优先级高的进程获得时间片长

(3)Linux进程优先级属性:nice(NI值)和Priority(PR值)

  • nice值
    • 范围是 -20 到 +19,默认值为0
    • nice值越大,优先级越低,获得的处理器时间越少
    • 也称静态优先级,即当nice值设定好了之后,除非我们用renice去改它,否则它是不变的
    • nice值是所有Unix系统的标准化的概念,不同的Unix系统nice值运用方式有所差异:在Mac OS中,nice值代表分配给进程时间片的绝对值,是绝对的。而Linux中,nice代表时间片的比例,是相对的
  • priority
    • 实时优先级,也称动态优先级,因为priority的值在之前内核的O1调度器上表现是会变化的
    • 默认情况下范围从0到99(包括0和99)
    • priority值越大,优先级越高
  • 重要Reference:https://blog.51cto.com/frankch/1773621

(4)实时进程和非实时进程(普通进程)

  • 在内核中,进程优先级的取值范围是通过一个宏定义的,这个宏的名称是MAX_PRIO,它的值为140。而这个值又是由另外两个值相加组成的,一个是代表nice值取值范围的NICE_WIDTH宏(-20到+19,共40),另一个是代表实时进程(realtime)优先级范围的MAX_RT_PRIO宏(0到99,共100)
  • 说白了就是,Linux实际上实现了140个优先级范围,取值范围是从0-139,这个值越小,优先级越高
  • 实时进程:优先级值在0-99范围内的,都是实时进程,可选择的调度策略:SCHED_FIFO、SCHED_RR(Round Robin)
  • 非实时进程 / 普通进程:100-139范围内的是非实时进程,对应策略有:SCHED_NORMAL、SCHED_OTHER、SCHED_IDLE
  • 重要Reference:https://blog.51cto.com/frankch/1773621

(5)时间片

  • 时间片太长,影响交互;时间片太短,容易造成上下文切换的消耗
  • 对于Linux来说,nice值影响每个进程的时间片

(6)一个例子

  • 假设存在两个进程:文字编辑进程(I/O消耗型进程) 和 视频编码进程(CPU消耗进程)
  • 对于文字编辑(I/O)进程:希望有更高的处理器时间(在Linux中即优先级更高),这并非因为它需要更多的处理器时间,而是我们希望在它需要时总能优先得到处理器
  • 理想的打算:分配给 IO进程 和 CPU进程 相同的nice值,因此它们时间片各占50%。每次使用时,IO进程使用时间不到50%会提前释放时间片,CPU进程用满50%。对于IO进程来说,它拥有更高的优先级;对于CPU进程来说,它拥有更长的使用时间。显然,这是公平的

4.4 Linux调度相关

(1)调度器:Linux有两个调度器一起工作

  • 主调度器 scheduler:一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU
  • 周期性调度器 scheduler_tick:另一种是通过周期性的机制, 以固定的频率运行, 不时的检测是否有必要

(2)调度策略(算法):六种

  • 实时进程:SCHED_FIFO,SCHED_RR,SCHED_DEADLINE(新支持的策略,基于EDF算法)
  • 普通进程:SCHED_NORMAL,SCHED_BATCH(也是基于CFS的,分化版本)
  • IDLE进程(可作了解,不重要):SCHED_IDLE(系统空闲时才跑这个调度算法)

(3)调度器类:五个

调度器类所属进程优先级描述对应调度策略
stop_sched_class最高优先级最高的线程,会中断所有其他线程,且不会被其他任务打断作用无, 不需要调度普通进程
dl_sched_class较高采用EDF最早截至时间优先算法调度实时进程SCHED_DEADLINE
rt_sched_class采用提供 Roound-Robin算法或者FIFO算法调度实时进程SCHED_FIFO, SCHED_RR
fair_sched_clas采用CFS算法调度普通的非实时进程SCHED_NORMAL, SCHED_BATCH
idle_sched_class采用CFS算法调度idle进程, 每个cup的第一个pid=0线程:swapper,是一个静态线程SCHED_IDLE

(4)总结

调度器类调度策略调度策略对应的调度算法调度实体调度实体对应的调度对象
stop_sched_class特殊情况, 发生在cpu_stop_cpu_callback 进行cpu之间任务迁移migration或者HOTPLUG_CPU的情况下关闭任务
dl_sched_classSCHED_DEADLINEEarliest-Deadline-First最早截至时间有限算法sched_dl_entity采用DEF最早截至时间有限算法调度实时进程
rt_sched_classSCHED_RR

SCHED_FIFO
Roound-Robin时间片轮转算法

FIFO先进先出算法
sched_rt_entity采用Roound-Robin或者FIFO算法调度的实时调度实体
fair_sched_classSCHED_NORMAL

SCHED_BATCH
CFS完全公平懂调度算法sched_entity采用CFS算法普通非实时进程
idle_sched_classSCHED_IDLE特殊进程, 用于cpu空闲时调度空闲进程idle

(5)Reference

Linux进程调度器概述--Linux进程的管理与调度(十五):https://blog.csdn.net/gatieme/article/details/51699889


4.5 CFS 完全公平调度算法

(1)CFS公平调度设计思路

  • 根据进程优先级权重(nice)分配运行时间
  • 分配给进程的运行时间 = 调度周期 * 进程权重 / 所有进程权重之和
  • 调度周期很好理解,就是将所有处于TASK_RUNNING态进程都调度一遍的时间,差不多相当于O(1)调度算法中运行队列和过期队列切换一次的时间
  • 举个例子,比如只有两个进程A, B,权重分别为1和2,调度周期设为30ms,那么分配给A的CPU时间为:30ms * (1/(1+2)) = 10ms;而B的CPU时间为:30ms * (2/(1+2)) = 20ms。那么在这30ms中A将运行10ms,B将运行20ms

(2)公平体现在哪——vruntime

  • 其实公平是体现在另外一个量上面,叫做virtual runtime(vruntime),它记录着进程已经运行的时间,但是并不是直接记录,而是要根据进程的权重将运行时间放大或者缩小一个比例
  • vruntime = 实际运行时间 * 1024 / 进程权重 
  • 为了不把大家搞晕,这里我直接写1024,实际上它等于nice为0的进程的权重,代码中是NICE_0_LOAD。也就是说,所有进程都以nice为0的进程的权重1024作为基准,计算自己的vruntime增加速度。
  • 还以上面AB两个进程为例,B的权重是A的2倍,那么B的vruntime增加速度只有A的一半

(3)CFS的内部实现,主要由四个部分组成,位于 kernel/sched_fair.c 中

  • 时间记账
    • CFS不再有时间片的概念,由vruntime变量来记录该进程的运行时间
  • 进程选择
    • 当CFS需要选择下一个运行进程时,它会挑一个具有最小vruntime的进程
    • CFS使用红黑树来组织可运行进程队列,迅速找到具有最小vruntime的进程(即最左侧的叶子节点)
  • 调度器入口
    • 在文件 kernel/sched.c 中,进程调度的主要入口点函数 schedule():在可运行队列中,选择哪个进程可以运行,何时投入运行
    • schedule()通常都需要和一个调度类相关联:找到优先级最高的调度类,询问该调度类谁是下一个该运行的进程
  • 睡眠和唤醒
    • 休眠(被阻塞)的进程处于一个不可执行状态,常见原因比如read()等。
    • 进入休眠时,内核把该进程标记成休眠状态,从可执行红黑树中移除。唤醒进程则相反
    • 第三章曾讨论过,休眠对应两种状态:TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE,区别在于
      • TASK_INTERRUPTIBLE:如果接收到一个信号,会被提前唤醒并响应该信号
      • TASK_UNINTERRUPTIBLE:如果接收到一个信号,会忽略
    • 休眠的进程在内核中用等待队列wake_queue_head_t来表示,需要休眠的进程进入该队列,被唤醒后再出来
    • 唤醒操作通过函数wake_up()进行,负责唤醒指定的等待队列上的所有进程

(4)Reference


4.6 抢占和上下文切换

(1)上下文切换

  • 概念:从一个可执行进程切换到另一个可执行进程
  • 对应函数:kernel/sched.c 中的 context_switch() 函数
    • 1. 调用switch_mm(), 把虚拟内存从一个进程映射切换到新进程中
    • 2. 调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息

(2)抢占:分为用户抢占和内核抢占

  • 用户抢占
    • 内核即将返回用户空间时(无论是【系统调用返回】还是【中断处理返回】),都会检查need_resched标志,如果它被设置了,内核会选择一个(更适合的)进程投入运行
    • 发生时机有两种
      • 从【系统调用】返回【用户空间】
      • 从【中断处理程序】返回【用户空间】
  • 内核抢占
    • 只要重新调度是安全的(没有锁),内核就能进行抢占
    • 每个进程的thread_info中有个preempt_count计数器,使用锁时+1,释放锁时-1。只有为0时才能安全抢占
    • 发生时机
      • 当从【中断处理程序】正在执行,且返回内核空间之前。当一个【中断处理例程】退出,在返回到内核态时(kernel-space)。这是隐式的调用schedule()函数,当前任务没有主动放弃CPU使用权,而是被剥夺了CPU使用权
      • 当内核代码再一次具有可抢占性的时候,如解锁(spin_unlock_bh)及使能软中断(local_bh_enable)等, 此时当kernel code从不可抢占状态变为可抢占状态时(preemptible again)。也就是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数
      • 如果内核中的任务显式的调用schedule(), 任务主动放弃CPU使用权
      • 如果内核中的任务阻塞(这同样也会导致调用schedule()), 导致需要调用schedule()函数。任务主动放弃CPU使用权
  • Reference

4.7 实时调度策略

见4.4的调度策略


Reference

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值