Linux-进程的管理与调度15(基于6.1内核)

基于6.1内核的Linux进程调度器解析

Linux-进程的管理与调度15(基于6.1内核)---Linux进程调度器

一、 调度器


1.1、 调度器定义


调度器(Scheduler) 是操作系统中负责管理和决定哪些进程或线程在何时运行的关键组件。它的主要任务是根据系统的调度策略,将可执行的进程或线程分配给 CPU 进行执行。调度器的目的是提高系统的响应性、资源利用率和公平性。

在现代操作系统中,调度器通常会管理多个任务(进程或线程)的执行,并根据一系列策略来决定哪个任务应该获得 CPU 时间。调度器是操作系统内核的一部分,通常在多任务操作系统中发挥重要作用。

调度器的基本功能

  1. 选择进程或线程:调度器决定哪个就绪(ready)状态的进程或线程应该被调度到 CPU 上执行。

  2. 分配 CPU 时间:调度器为选择的进程或线程分配一定的时间片(time slice),也就是在多任务环境下,控制每个任务运行的最大时间长度。

  3. 任务切换:当一个任务的时间片用完,或者发生了某些事件(如 I/O 操作完成),调度器会决定哪个任务接着执行,执行任务切换。

  4. 优先级管理:调度器会根据任务的优先级,调整不同进程或线程的调度顺序。高优先级的任务可能会优先获得 CPU 时间。

  5. 进程状态管理:调度器还需要管理进程的各种状态(如就绪、运行、阻塞等),并根据不同的状态变化做出相应的调度决策。

1.2、 调度策略


根据调度策略和方式,调度器有不同的类型:

  1. 长程调度(Long-Term Scheduling):决定哪些新创建的进程应该进入内存,开始执行。长程调度的目的是管理系统中进程的数量,控制负载。

  2. 中程调度(Medium-Term Scheduling):有时操作系统会将进程从内存中移到磁盘(比如换出操作),以避免过度占用内存。中程调度负责决定哪些进程需要被换出,哪些进程需要被换入。

  3. 短程调度(Short-Term Scheduling):也叫CPU 调度,决定哪个就绪队列中的进程(或线程)被调度到 CPU 上执行。短程调度频繁发生,通常是调度器的核心功能。短程调度是操作系统调度的最重要部分,常用的策略有以下几种:

    • 先来先服务(FCFS, First-Come, First-Served):按进程到达的顺序调度执行。
    • 短作业优先(SJF, Shortest Job First):优先执行预计运行时间最短的进程。
    • 时间片轮转(RR, Round Robin):每个进程轮流获得一定的时间片执行,时间片用完后就切换到下一个进程。
    • 优先级调度:根据进程的优先级来决定执行顺序,优先级高的进程优先执行。
    • 多级反馈队列(MLFQ, Multi-Level Feedback Queue):结合多个调度策略,根据进程的行为(如执行时间、阻塞情况)动态调整优先级。

1.3、 进程饥饿


**进程饥饿(Starvation)**是指在多进程调度环境中,某些进程长时间无法获得 CPU 时间进行执行,导致它们无法完成或推迟其执行。进程饥饿通常是由于调度策略的不公平性或优先级调度机制导致某些进程一直无法得到调度的机会。

进程饥饿的原因

  1. 优先级调度中的低优先级进程

    • 在优先级调度中,高优先级的进程可能会频繁获得 CPU 时间,而低优先级的进程则可能长时间得不到执行。这种情况通常发生在优先级调度采用的是静态优先级,即优先级在进程的生命周期内不发生变化。
    • 如果高优先级的进程不断到达,就会导致低优先级进程一直被推迟执行,这就是典型的进程饥饿现象。
  2. 短作业优先(SJF)调度中的长作业

    • 在短作业优先调度策略下,系统会优先执行预计执行时间较短的进程。这会导致长作业得不到及时的调度,尤其是在有大量短作业进程到达的情况下。长期存在这种情况时,长作业可能一直处于等待状态,从而导致饥饿。
    • 这是 SJF 调度策略的一个典型问题,特别是在没有适当处理长作业的情况下。
  3. 时间片轮转(RR)中的上下文切换开销

    • 虽然时间片轮转策略本身是公平的,但如果时间片设置得过小,会导致频繁的上下文切换,从而引发较大的系统开销。如果大部分进程是短任务或者阻塞任务,而只有少数进程需要大量 CPU 时间,它们就可能因为时间片的切换而长时间得不到执行,导致饥饿现象。
  4. 资源竞争

    • 在某些情况下,进程可能会因为竞争某些共享资源(如 CPU、内存、I/O 设备等)而导致饥饿。如果系统没有有效地管理资源分配,某些进程可能会长时间得不到资源,特别是在高优先级进程频繁占用这些资源时。
  5. 多级反馈队列(MLFQ)中的优先级提升/降级机制

    • 在多级反馈队列调度策略中,进程根据其运行情况被动态地分配到不同优先级的队列中。若进程长时间占用 CPU 或表现较差,可能会被移至较低优先级的队列,从而遭遇饥饿,特别是当队列中的其他高优先级进程不断到达时。

二、Linux进程的分类


2.1、 进程的分类


当涉及有关调度的问题时, 传统上把进程分类为”I/O受限(I/O-dound)”或”CPU受限(CPU-bound)”.

类型别称描述示例
I/O受限型I/O密集型频繁的使用I/O设备, 并花费很多时间等待I/O操作的完成数据库服务器, 文本编辑器
CPU受限型计算密集型花费大量CPU时间进行数值计算图形绘制程序

另外一种分类法把进程区分为三类:

类型描述示例
交互式进程(interactive process)此类进程经常与用户进行交互, 因此需要花费很多时间等待键盘和鼠标操作. 当接受了用户的输入后, 进程必须很快被唤醒, 否则用户会感觉系统反应迟钝shell, 文本编辑程序和图形应用程序
批处理进程(batch process)此类进程不必与用户交互, 因此经常在后台运行. 因为这样的进程不必很快相应, 因此常受到调度程序的怠慢程序语言的编译程序, 数据库搜索引擎以及科学计算
实时进程(real-time process)这些进程由很强的调度需要, 这样的进程绝不会被低优先级的进程阻塞. 并且他们的响应时间要尽可能的短视频音频应用程序, 机器人控制程序以及从物理传感器上收集数据的程序

2.2、 实时进程与普通进程


根据进程的不同分类Linux采用不同的调度策略.

对于实时进程,采用FIFO或者Round Robin的调度策略.

对于普通进程,则需要区分交互式和批处理式的不同。传统Linux调度器提高交互式应用的优先级,使得它们能更快地被调度。而CFS和RSDL等新的调度器的核心思想是”完全公平”。这个设计理念不仅大大简化了调度器的代码复杂度,还对各种调度需求的提供了更完美的支持。

特征/分类实时进程(Real-Time Process)普通进程(Non-Real-Time Process)
时间要求严格的时间限制(硬实时/软实时)对时间要求较松散,系统可接受延迟
调度机制实时调度算法(如 RMS、EDF),确保及时执行时间共享调度算法(如轮转调度、优先级调度)
资源保障系统保证资源分配,避免延迟资源共享,调度策略较为灵活
应用场景控制系统、医疗设备、实时通信、音视频处理等普通桌面应用、数据库、浏览器等
系统稳定性要求高,需要保证在特定时间内完成任务稳定性要求较低,任务可接受延迟

三、 Linux调度器的演变


Linux 调度器(Linux Scheduler)是负责管理和调度系统中各个进程的核心部分,它确保 CPU 时间被合理分配给各个进程。Linux 的调度器已经经历了多次重要的演变,从早期的简单算法到现代的多队列调度器,Linux 调度器的发展与操作系统的需求、硬件发展以及多核系统的普及密切相关。

版本特性及变化
0.99 - 2.2基于优先级的简单调度
2.4引入 O(1) 调度算法,支持多核和 SMP(对称多处理)
2.6引入 CFS(完全公平调度器),支持公平调度和低延迟
3.x多核优化,NUMA 支持,实时调度改进
4.x / 5.x大规模多核支持,能源效率优化,调度器可扩展性增强
6.x 及未来实时性和低延迟优化,智能调度和多线程调度优化

四、 Linux的调度器设计


4.1、 linux进程调度器的框架


2个调度器

可以用两种方法来激活调度

  • 一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU。

  • 另一种是通过周期性的机制, 以固定的频率运行, 不时的检测是否有必要。

因此当前linux的调度程序由两个调度器组成:主调度器周期性调度器(两者又统称为通用调度器(generic scheduler)核心调度器(core scheduler))。

并且每个调度器包括两个内容:调度框架(其实质就是两个函数框架)及调度器类。

6种调度策略

linux内核目前实现了6中调度策略(即调度算法), 用于对不同类型的进程进行调度, 或者支持某些特殊的功能

比如SCHED_NORMALSCHED_BATCH调度普通的非实时进程, SCHED_FIFOSCHED_RRSCHED_DEADLINE则采用不同的调度策略调度实时进程, SCHED_IDLE则在系统空闲时调用idle进程.

字段描述所在调度器类
SCHED_NORMAL(也叫SCHED_OTHER)用于普通进程,通过CFS调度器实现。SCHED_BATCH用于非交互的处理器消耗型进程。SCHED_IDLE是在系统负载很低时使用CFS
SCHED_BATCHSCHED_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

linux内核实现的6种调度策略, 前面三种策略使用的是cfs调度器类,后面两种使用rt调度器类, 最后一个使用DL调度器类。

简易版:

调度策略用途主要特性
SCHED_OTHER默认策略,适用于普通进程公平调度,非实时,基于 CFS 调度
SCHED_FIFO实时进程,适用于高优先级任务先到先服务,不允许抢占
SCHED_RR实时进程,适用于时间片轮转任务时间片轮转,轮流调度,不允许抢占
SCHED_DEADLINE实时任务,硬实时任务调度基于任务截止时间的调度,严格的时间控制
SCHED_IDLE用于系统空闲时运行的低优先级进程只在系统空闲时运行,优先级最低
SCHED_BATCH长时间运行的批处理任务,不要求快速响应优先级较低,适合长时间计算密集型任务

5个调度器类

而依据其调度策略的不同实现了5个调度器类, 一个调度器类可以用一种种或者多种调度策略调度某一类进程, 也可以用于特殊情况或者调度特殊功能的进程.

调度器类描述对应调度策略
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异常的时候做dumpSCHED_IDLE

其所属进程的优先级顺序为:

stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class

3个调度实体

调度器不限于调度进程, 还可以调度更大的实体, 比如实现组调度: 可用的CPUI时间首先在一半的进程组(比如, 所有进程按照所有者分组)之间分配, 接下来分配的时间再在组内进行二次分配。

这种一般性要求调度器不直接操作进程, 而是处理可调度实体, 因此需要一个通用的数据结构描述这个调度实体,即seched_entity结构, 其实际上就代表了一个调度对象,可以为一个进程,也可以为一个进程组。

Linux中针对当前可调度的实时和非实时进程, 定义了类型为seched_entity的3个调度实体:

调度实体名称描述对应调度器类
sched_dl_entityDEADLINE调度实体采用EDF算法调度的实时调度实体dl_sched_class
sched_rt_entityRT调度实体采用Roound-Robin或者FIFO算法调度的实时调度实体rt_sched_class
sched_entityCFS调度实体采用CFS算法调度的普通非实时进程的调度实体fair_sched_class

调度器类的就绪队列

另外,对于调度框架及调度器类,它们都有自己管理的运行队列,调度框架只识别rq(其实它也不能算是运行队列),而对于cfs调度器类它的运行队列则是cfs_rq内部使用红黑树组织调度实体),实时rt的运行队列则为rt_rq内部使用优先级bitmap+双向链表组织调度实体), 此外内核对新增的dl实时调度策略也提供了运行队列dl_rq。

调度器整体框架

本质上, 通用调度器(核心调度器)是一个分配器,与其他两个组件交互。

  • 调度器用于判断接下来运行哪个进程。
    内核支持不同的调度策略(完全公平调度, 实时调度, 在无事可做的时候调度空闲进程,即0号进程也叫swapper进程,idle进程), 调度类使得能够以模块化的方法实现这些侧露额, 即一个类的代码不需要与其他类的代码交互。
    当调度器被调用时, 他会查询调度器类, 得知接下来运行哪个进程。

  • 在选中将要运行的进程之后, 必须执行底层的任务切换。
    这需要与CPU的紧密交互. 每个进程刚好属于某一调度类, 各个调度类负责管理所属的进程. 通用调度器自身不涉及进程管理, 其工作都委托给调度器类。

每个进程都属于某个调度器类(由字段task_struct->sched_class标识), 由调度器类采用进程对应的调度策略调度(由task_struct->policy )进行调度, task_struct也存储了其对应的调度实体标识。

Linux实现了6种调度策略, 依据其调度策略的不同实现了5个调度器类, 一个调度器类可以用一种或者多种调度策略调度某一类进程, 也可以用于特殊情况或者调度特殊功能的进程。

调度器类调度策略调度策略对应的调度算法调度实体调度实体对应的调度对象
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种调度器类为什么只有3种调度实体

正常来说一个调度器类应该对应一类调度实体, 但是5种调度器类却只有了3种调度实体?

这是因为调度实体本质是一个可以被调度的对象, 要么是一个进程(linux中线程本质上也是进程), 要么是一个进程组, 只有dl_sched_class, rt_sched_class调度的实时进程(组)以及fair_sched_class调度的非实时进程(组)是可以被调度的实体对象, 而stop_sched_class和idle_sched_class

为什么采用EDF实时调度需要单独的调度器类, 调度策略和调度实体

linux针对实时进程实现了Roound-Robin, FIFO和Earliest-Deadline-First(EDF)算法, 但是为什么SCHED_RR和SCHED_FIFO两种调度算法都用rt_sched_class调度类和sched_rt_entity调度实体描述, 而EDF算法却需要单独用rt_sched_class调度类和sched_dl_entity调度实体描述

为什么采用EDF实时调度不用rt_sched_class调度类调度, 而是单独实现调度类和调度实体?

4.2、 进程的调度


首先,我们需要清楚,什么样的进程会进入调度器进行选择,就是处于TASK_RUNNING状态的进程,而其他状态下的进程都不会进入调度器进行调度。
系统发生调度的时机如下:

  • 调用cond_resched()时

  • 显式调用schedule()时

  • 从系统调用或者异常中断返回用户空间时

  • 从中断上下文返回用户空间时

当开启内核抢占(默认开启)时,会多出几个调度时机,如下:

  • 在系统调用或者异常中断上下文中调用preempt_enable()时(多次调用preempt_enable()时,系统只会在最后一次调用时会调度)

  • 在中断上下文中,从中断处理函数返回到可抢占的上下文时(这里是中断下半部,中断上半部实际上会关中断,而新的中断只会被登记,由于上半部处理很快,上半部处理完成后才会执行新的中断信号,这样就形成了中断可重入)

而在系统启动调度器初始化时会初始化一个调度定时器,调度定时器每隔一定时间执行一个中断,在中断会对当前运行进程运行时间进行更新,如果进程需要被调度,在调度定时器中断中会设置一个调度标志位,之后从定时器中断返回,因为上面已经提到从中断上下文返回时是有调度时机的,在内核源码的汇编代码中所有中断返回处理都必须去判断调度标志位是否设置,如设置则执行schedule()进行调度。

而我们知道实时进程和普通进程是共存的,调度器是怎么协调它们之间的调度的呢,其实很简单,每次调度时,会先在实时进程运行队列中查看是否有可运行的实时进程,如果没有,再去普通进程运行队列找下一个可运行的普通进程,如果也没有,则调度器会使用idle进程进行运行。

系统并不是每时每刻都允许调度的发生,当处于硬中断期间的时候,调度是被系统禁止的,之后硬中断过后才重新允许调度。而对于异常,系统并不会禁止调度,也就是在异常上下文中,系统是有可能发生调度的。

4.3、 抢占标识TIF_NEED_RESCHED

内核在检查need_resched标识TIF_NEED_RESCHED的值判断是否需要抢占当前进程, 内核在thread_info的flag中设置了一个标识来标志进程是否需要重新调度, 即重新调度need_resched标识TIF_NEED_RESCHED, 内核在即将返回用户空间时会检查标识TIF_NEED_RESCHED标志进程是否需要重新调度。

系统中每个进程都有一个特定于体系结构的struct thread_info结构, 用户层程序被调度的时候会检查struct thread_info中的need_resched标识TLF_NEED_RESCHED标识来检查自己是否需要被重新调度。

如果内核检查进程的抢占标识被设置, 则会在一个关键的时刻, 调用调度器来完成调度和抢占的工作。

4.4、 内核抢占和用户抢占


而根据进程抢占发生的时机, 抢占可以分为内核抢占和用户抢占, 内核抢占就是指一个在内核态运行的进程, 可能在执行内核函数期间被另一个进程:

一般来说,用户抢占发生几下情况:

  • 从系统调用返回用户空间;

  • 从中断(异常)处理程序返回用户空间

内核抢占发生的时机,一般发生在:

  • 当从中断处理程序正在执行,且返回内核空间之前。当一个中断处理例程退出,在返回到内核态时(kernel-space)。这是隐式的调用schedule()函数,当前任务没有主动放弃CPU使用权,而是被剥夺了CPU使用权。

  • 当内核代码再一次具有可抢占性的时候,如解锁(spin_unlock_bh)及使能软中断(local_bh_enable)等, 此时当kernel code从不可抢占状态变为可抢占状态时(preemptible again)。也就是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数

  • 如果内核中的任务显式的调用schedule(), 任务主动放弃CPU使用权

  • 如果内核中的任务阻塞(这同样也会导致调用schedule()), 导致需要调用schedule()函数。任务主动放弃CPU使用权

内核抢占采用同抢占标识的类似方法被实现, linux内核在thread_info结构中添加了一个自旋锁标识preempt_count, 称为抢占计数器(preemption counter)

struct thread_info
{
    /*  ......  */
    int preempt_count;   /* 0 => preemptable, <0 => BUG */
    /*  ......  */
}
preempt_count值描述
>0禁止内核抢占, 其值标记了使用preempt_count的临界区的数目
=0开启内核抢占
<0锁为负值, 内核出现错误

内核自然也提供了一些函数或者宏, 用来开启, 关闭以及检测抢占计数器preempt_coun的值:include/asm-generic/preempt.h。

4.5、 周期性调度器scheduler_tick


周期调度器(Cyclic Scheduler)通常用于实时操作系统(RTOS)或特定的调度框架中,它旨在按照预定的时间间隔或周期执行任务。与传统的基于优先级的调度器不同,周期调度器根据任务的周期性要求来安排任务的执行,确保任务在规定的周期内按时完成。

在 Linux 内核中,周期调度器并不直接作为一个独立的调度器存在,但 Linux 的调度系统通过一些实时调度策略(如 SCHED_FIFOSCHED_RR)可以实现周期任务的调度。此外,Linux 还提供了用于周期性任务的机制,比如定时器(timer)和内核的定时任务框架(例如 hrtimers)。

4.6、 主调度器schedule


schedule就是主调度器的工作函数, 在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule或者其子函数__schedule.

__schedule完成抢占

  • 完成一些必要的检查, 并设置进程状态, 处理进程所在的就绪队列

  • 调度全局的pick_next_task选择抢占的进程

    如果当前cpu上所有的进程都是cfs调度的普通非实时进程, 则直接用cfs调度, 如果无程序可调度则调度idle进程

    否则从优先级最高的调度器类sched_class_highest(目前是stop_sched_class)开始依次遍历所有调度器类的pick_next_task函数, 选择最优的那个进程执行

  • context_switch完成进程上下文切换

    调用switch_mm(), 把虚拟内存从一个进程映射切换到新进程中

    调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息

4.7、 进程上下文切换context_switch


context_switch其实是一个分配器, 他会调用所需的特定体系结构的方法

  • 调用switch_mm(), 把虚拟内存从一个进程映射切换到新进程中

    switch_mm更换通过task_struct->mm描述的内存管理上下文, 该工作的细节取决于处理器, 主要包括加载页表, 刷出地址转换后备缓冲器(部分或者全部), 向内存管理单元(MMU)提供新的信息

  • 调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息

    switch_to切换处理器寄存器的呢内容和内核栈(虚拟地址空间的用户部分已经通过switch_mm变更, 其中也包括了用户状态下的栈, 因此switch_to不需要变更用户栈, 只需变更内核栈), 此段代码严重依赖于体系结构, 且代码通常都是用汇编语言编写.

为什么switch_to需要3个参数

在新进程被选中执行时, 内核恢复到进程被切换出去的点继续执行, 此时内核只知道谁之前将新进程抢占了, 但是却不知道新进程再次执行是抢占了谁, 因此底层的进程切换机制必须将此前执行的进程(即新进程抢占的那个进程)提供给context_switch. 由于控制流会回到函数的该中间, 因此无法通过普通函数的返回值来完成. 因此使用了一个3个参数, 但是逻辑效果是相同的, 仿佛是switch_to是带有两个参数的函数, 而且返回了一个指向此前运行的进程的指针.

switch_to(prev, next, last);


prev = last = switch_to(prev, next);

其中返回的prev值并不是做参数的prev值, 而是prev被再次调度的时候抢占掉的那个进程last。

4.8、 处理进程优先级


在 Linux 内核中,任务的优先级通过一个从 0 到 139 的数值表示,数值越小优先级越高。实时任务(如使用 SCHED_FIFO 和 SCHED_RR 策略的任务)具有较低的优先级数值(0 到 99),而普通任务则在 100 至 139 范围内,数值越大优先级越低。内核通过实时调度策略和普通调度策略来管理任务的执行顺序,并根据任务的优先级来决定哪个任务获得 CPU 执行时间。

从0~99的范围专供实时进程使用, nice的值[-20,19]则映射到范围100~139。

其中task_struct采用了三个成员表示进程的优先级:prio和normal_prio表示动态优先级, static_prio表示进程的静态优先级。

此外还用了一个字段rt_priority保存了实时进程的优先级。

字段描述
static_prio用于保存静态优先级, 是进程启动时分配的优先级, ,可以通过nice和sched_setscheduler系统调用来进行修改, 否则在进程运行期间会一直保持恒定
prio保存进程的动态优先级
normal_prio表示基于进程的静态优先级static_prio和调度策略计算出的优先级. 因此即使普通进程和实时进程具有相同的静态优先级, 其普通优先级也是不同的, 进程分叉(fork)时, 子进程会继承父进程的普通优先级
rt_priority用于保存实时优先级, 实时进程的优先级用实时优先级rt_priority来表示

静态优先级static_prio(普通进程)和实时优先级rt_priority(实时进程)是计算的起点。

内核通过effective_prio设置动态优先级prio, 计算动态优先级的流程如下:

  • 设置进程的普通优先级(实时进程99-rt_priority, 普通进程为static_priority)

  • 计算进程的动态优先级(实时进程则维持动态优先级的prio不变, 普通进程的动态优先级即为其普通优先级)

最后, 我们综述一下在针对不同类型进程的计算结果:

进程类型实时优先级rt_priority静态优先级static_prio普通优先级normal_prio动态优先级prio
EDF调度的实时进程rt_priority不使用MAX_DL_PRIO-1维持原prio不变
RT算法调度的实时进程rt_priority不使用MAX_RT_PRIO-1-rt_priority维持原prio不变
普通进程不使用static_priostatic_priostatic_prio
优先级提高的普通进程不使用static_prio(改变)static_prio维持原prio不变

4.9、 唤醒抢占


Linux 内核中,唤醒抢占是调度机制中的一部分,具体体现在 抢占式调度(preemptive scheduling)中。下面是关于唤醒抢占的详细解析:

1. 抢占式调度(Preemptive Scheduling)

Linux 内核使用抢占式调度来管理任务的执行。抢占式调度意味着当一个任务正在执行时,如果有更高优先级的任务变为可执行,当前任务将被中断,调度器会立即调度更高优先级的任务运行。

抢占的触发时机 通常包括:

  • 当前任务的时间片用完。
  • 当前任务自愿放弃 CPU(例如通过 sleep()wait() 系统调用)。
  • 由于系统负载或其他条件,调度器决定将 CPU 分配给更高优先级的任务。
  • 当一个新的高优先级进程被唤醒时,可能会发生抢占。

2. 唤醒抢占

“唤醒抢占”是指当某个进程从休眠状态唤醒时,内核会检查当前的调度情况,并决定是否允许这个新唤醒的进程立即抢占当前正在运行的进程。

2.1 唤醒时的判断

当一个进程被唤醒时,调度器会执行以下操作:

  • 检查新唤醒进程的优先级:唤醒的进程会根据它的调度优先级与当前正在运行的进程进行比较。
  • 如果唤醒进程的优先级更高,那么当前运行的进程会被抢占,唤醒的进程将会立即获得 CPU 时间,执行调度。
  • 如果唤醒进程的优先级较低,则唤醒进程会按照调度策略进入就绪队列,等待适当的时机被调度执行。

2.2 唤醒与抢占的关系

在 Linux 内核中,唤醒操作通常由系统调用如 wake_up()wake_up_all() 触发。这些操作会将一个或多个进程从休眠状态唤醒,放入就绪队列。如果此时有更高优先级的进程,就可能发生抢占,当前正在执行的进程会被挂起,直到新的高优先级进程执行完毕。

  • 例子: 假设有两个进程:进程 A(优先级较低)和进程 B(优先级较高)。当进程 B 被唤醒时,如果进程 A 正在执行,调度器会抢占进程 A,让进程 B 先执行。

2.3 Linux 内核中的实现

在 Linux 中,抢占式调度是通过对进程上下文的切换来实现的。Linux 调度器会在每次调度时检查当前运行的进程和就绪队列中的进程,决定是否需要进行抢占。

  1. 唤醒进程:当进程从休眠或等待状态变为就绪状态时,调度器会检查这个进程的优先级。
  2. 调度判断:如果唤醒的进程的优先级比当前正在运行的进程高,调度器会执行抢占操作,暂停当前进程,并将 CPU 资源分配给高优先级的进程。

3. 唤醒与抢占的示例

假设有一个系统包含以下进程:

  • 进程 A(优先级 10)
  • 进程 B(优先级 5,当前正在运行)
  • 进程 C(优先级 1,刚刚被唤醒)

此时,进程 C 被唤醒。根据优先级:

  • 如果进程 C 的优先级(1)比进程 B 的优先级(5)高,进程 C 会立即抢占进程 B,调度器会暂停进程 B,分配 CPU 给进程 C 执行。
  • 如果进程 C 的优先级低于进程 B,调度器会将进程 C 放入就绪队列,等到进程 B 完成后,再调度进程 C 执行。

4. 抢占与内核模式

在 Linux 中,内核模式用户模式的进程抢占行为有所不同:

  • 内核模式下的抢占:如果当前进程正在内核模式下执行,抢占可能会受到限制。在某些情况下,为了保证内核操作的完整性,内核会暂时禁用抢占,直到内核态代码执行完成。
  • 用户模式下的抢占:当进程处于用户模式时,抢占通常是开启的,即使当前任务正在执行,内核会检查是否有更高优先级的任务需要执行,并进行相应的抢占。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值