linux 下通用调度框架解析

一 . 前言

1. 为什么分析通用linux下通用调度器,

首先说下结论,调度性能是决定一个操作系统性能优劣的最重要标准之一, 从用户层面上看,如果调度系统设计不好,那么就会造成卡顿现象,就像是你打开一个app,但是这个app却半天也没有反应,这很大可能是因为调度系统造成的,因为从用户角度出发,交互式应用应该是要有较大的优先级,这样它才可能较快获得操作系统的使用权,但是操作系统很可能把这个app认为是一般app,所以它的优先性没有得到保障,这就是造成卡顿的原因之一。历史上linux调度器出现过许多调度算法,到现在基本上是比较稳定,也得到较多的认可,本文分析是基于linux4.19内核,硬件架构是基于arm64。在这个版本下主要调度算法是基于优先级的实时调度算法和基于完全公平调度策略的完全公平调度算法。

二 . linux通用调度器的构成

对操作有深入了解的人都听过这样一句话,操作系统就是由中断驱动的死循环。我对于这句话还是深信不疑的,因为不论在那种操作系统中,中断都是具有最高优先权的,它可以打断任何进程的执行,在一个实时操作系统中,为了获得较低的中断延时,一般实时应用大多数时间都在睡眠状态,它由外部中断或者低优先级的任务唤醒,当唤醒实时任务后,它可以立即抢占低优先级的任务,从而达到较好的实时性,同时也就要求在中断唤醒实时任务后不能处理太多的事物,否则中断延迟就会增大,降低实时性,但是linux并不是一个硬实时的操作系统,它是一个软实时的操作系统,所以linux对唤醒任务采取的策略是延迟调度的策略,所谓延迟调度并不是说把进程的调度标记一经设置就立马去执行调度,而是把调度标记和调度执行分开进行。linux一般是在时钟中断中设置调度标记,然后在中断返回时执行调度

根据是否是进程自愿放弃cpu使用权,我们把调度分为主动调度和抢占调度,主动调度就是进程自愿放弃cpu使用权,从而把cpu让给运行队列中下一个可运行进程使用,而抢占调度是由中断发起的强制把cpu使用权让给运行队列中下一个运行进程使用,来防止某一个进程永久霸占cpu。所以linux调度系统就是按照这个原则设计的,在时钟中断或者外设中断中到来时设置调度标记,然后在中断返回时进行抢占调度,其中调度标记的设置由函数schedule_tick执行,而调度则是由schedule函数执行,本文则是重点分析这两个函数

三 . linux主动调度与抢占调度

3-1主动调度

何为主动调度?,就是进程自愿让出cpu使用权,那么操作系统就会寻找下一个可以运行的进程,在linux中主动调度很常见,因为在很多时候进程需要睡眠或者进程改变了自己的运行状态,一般来说主动调度意味着当前进程把自己的运行状态从可以运行设置成不可运行,然后调用schedule函数完成进程调度工作,下面是会进行主动调度的几个场景,

3-1-1 进程主动调用sleep函数

进程在调用sleep函数后首先把自己的运行状态设置成TASK_INTERRUPTIBLE,即可被中断的睡眠态,然后调用schedule函数,自此进程不在出现在运行队列中,只能等待别的进程或者中断唤醒它。

3-1-2 进程在驱动程序中等待资源时

我们知道驱动是代表进程在内核态运行,此时有可能驱动程序要读写的资源还没有到位,比如在socket程序中调用read函数可能会休眠,因为可能要读的文件中没有数据,那么驱动会把当前进程放入一个等待队列中,直到文件中有数据,那么当前进程会被唤醒,并加入到运行队列等待调度。

3-1-3 进程在等待信号量时

这是在进程中有很大概率发生的,因为信号量主要用于同步,当前进程如果没有获得信号量那么就会把当前进程加入到信号量的等待队列中,直到有进程释放信号量,在信号量释放函数中可能唤醒当前进程,获取信号量函数为void down(struct semaphore * sem);可能让进程睡眠,而释放信号量函数为void up(struct semaphore * sem);可能会唤醒等待此信号量的进程

3-1-4 进程调用 exit 或者wake_up() 

进程在调用exit函数时,进程状态就由运行状态变为退出状态,此时在exit内部会进行一系列清理动作,并且发送信号给其父进程,好让其父进程执行清理动作,此时会把进程从运行队列删除,并且回收其task_struct结构体,这个进程就消亡了,只能等待别的进程再一次创建它。

当一个进程试图唤醒另外一个进程是很有可能导致自己被抢占,因为被唤醒的进程的优先级可能比自己高,所以在唤醒别的进程时要注意这一点。

3-2 抢占调度

3-2-1内核抢占

3-2-1-1 内核抢占原理

       首先说明内核抢占是为了加强linux的实时性而在linux 2.6版本加入的一个特性,可以在编译内核时选择是否启用这个特性。什么是内核抢占?,我们说进程可以运行在用户空间,也可以运行在内核空间,如果不考虑中断和异常,那么进程运行在内核空间就只能是通过系统调用来完成,此时我们说内核代表用户进程运行于内核空间。而如果当前有一个实时进程被唤醒,那么它能快速得到运行吗?可能不一定,因为可能当前进程可能是运行于内核态,这个时候实时进程得等到系统调用返回到用户空间是才能得到调度,那如果系统调用时间很长,岂不是实时性无法保证?,居于这个原因,linux提出内核抢占技术,内核抢占是说高优先级进程(比如一个实时进程)可以抢占一个处于内核态的用户进程,或者换一种说法:实时进程可以抢占系统调用。当然也不是什么时候也都能执行内核抢占,我们的程序可以在关键的地方禁止内核抢占,怎么禁止?,当然不是在编译时通过不开启内核抢占这个特性禁止,linux在task_struct中设置一个计数量,名为preempt_count ,当这个计数量为0时表明可以抢占内核态,否则不能抢占内核态。

3-2-1-2 preempt_enable

preempt_enable用来设置抢占调度,在这个函数中我们会去试图把preempt_count 减1,如果它减1后仍然为0,并且需要调度标志存在,那么我们执行调度,此时不管用户进程是出于内核态还是用户态,如下图为preempt_enable代码

3-2-1-3 preempt_disable 

preempt_disable可以用来在一些内核函数中禁止内核抢占,因为我们要确保一些内核态代码不能被用户进程打断,此时就要调用这个函数,它只是单纯把抢占计数减1,如下代码所示

3-2-2 用户抢占

 相对于内核抢占,用户抢占是必须支持的,用户抢占是指一个用户进程抢占另一个处于用户态的进程,因为操作系统必须处理可能出现的某一个进程长期霸占cpu的现象,就是说必须处理某一个进程不主动放弃cpu的情况,那么出现这种情况怎么办?,好办,就是操作系统强制把你的cpu使用权剥夺,这就是被动调度,也称抢占调度,通常这会出现在时钟中断或者有外设中断唤醒高优先级任务时,所以主要分析这两种情况。

3-2-2-1 时钟中断中调用 schedule_tick

        我们知道操作系统底层的驱动因素就是时钟中断,也称tick中断,它就是一个能产生周期性中断的定时器,在每一次时钟中断到来之际,它都会做一些必须的工作,在linux中,不管是什么架构还是哪个版本linux操作系统,在时钟中断中都会调用schedule_tick函数,在这个函数中更新当前进程的时间计数和维护当前cpu的运行队列,schedule_tick函数更新当前进程的运行时间,和更新实时进程的时间片(如果是时间片轮转的实时进程的话),如果是实时进程,就判断其时间片有没有执行完,如果当前实时进程时间片执行完那么会给当前进程设置需要调度标记,在时钟中断返回用户态时进行调度,从而让出cpu,而对于普通进程,由于普通进程是共享cpu运行时间,所以在schedule_tick函数中会进行时间判断,即当前进程时间片是否用完,或者当前进程的虚拟运行时间是否是最小,如果不是最小,那么也会设置需要调度标记,在时钟中断返回用户态时进行调度,从而强迫当前cpu让出时间片。

3-2-2-2 在外设中断中唤醒高优先级进程

         如果一个依赖于外部中断唤醒的进程被唤醒,那么它的调度过程是什么样呢?,这个问题其实很常见,如一个依赖于gpio高电平信号的进程,这个进程在gpio高电平到来时会进行数据处理,这个任务优先级通常比较高,所以它不会经常运行,所以它依赖于中断唤醒并可能快速执行,在gpio中断中我们唤醒实时进程,其实就是把实时进程的状态从睡眠态设置成运行态,并把它加入当前cpu的运行队列,并设置调度标记,这样当中断退出时,由于唤醒的进程具有较高的优先级,所以它可以抢占当前的用户进程

四 schedule_tick函数详解

scheduler_tick函数是被周期性调用,它的调用回溯如下图

可以看到它是被本地cpu的时钟中断周期性调用,在这里它最主要的功能就是有三个,如下图。

(1) 更新当前运行队列和进程时间戳

(2) 根据当前进程调度策略和时间戳决定是否设置调度标志

(3) 更新cpu负载值,根据负载值决定是否触发负载均衡

下图为完整代码

其中最重要的是标有(2)的函数,它根据当前进程所属的调度类执行对应的task_tick函数,由于linux共有五个不同调度类,他们优先级由高到低如下图

所以下面我们分析这五个调度类的task_tick函数,这个函数决定是否设置调度标记,周期调度器的核心函数之一,即如果设置了调度标记那么在时钟中断返回用户态时就可以进行调用schedule函数,从而达到抢占调度的目的。

1.stop_sched_class调度类

stop_sched_class调度类具有最高优先级,一般进程不能设置为这个调度类,它的task_tick指向的函数为task_tick_stop函数,下图为其源代码

可以看到这个函数为空,所以时钟中断不会设置它的调度标记,所以意味着如果时钟中断打断了stop_sched_class的进程,那时钟中断返回时将不会执行调度函数schedule,意味着具有stop_sched_class类的进程只能是自己放弃cpu,否则它将一直运行,这正体现其具有最高优先级的特点,所以我们要谨慎把进程设置成stop_sched_class调度类。

2.dl_sched_class调度类

dl_sched_class调度类是优先级第二的调度类,它又叫做最后期限调度类,是为了弥补实时调度类的不足才设计得调度类,他会在规定的期限之前完成进程调度,所以称为最后期限调度,它的task_tick指向task_tick_dl函数,代码如下

可以看到它会有可能设置调度标记,然后再中断返回时被强占。

3.rt_sched_class调度类

实时调度支持两种调度策略,分别是时间片轮转调度和先进先出调度,它们的区别就是时间片轮转调度会有时间片,而先进先出调度没有时间片,只能是自己主动让出时间片,否则同一个优先级的进程不会得到调度,它的task_tick指向task_tick_rt函数,如下图

这个函数在对时间片轮转调度的进程中可能会设置调度标志,说明现在当前进程时间片已经用完,要进行调度。

4.fair_sched_class调度类

linux的进程大多数都属于这个调度类,因为linux只是一个通用操作系统,它的大多数进程都是普通进程,不会有特殊的调度要求,只是在突发情况下可能把进程的优先级暂时升高。当在shell中执行命令或者脚本时,创建的进程就是fair_sched_class类进程,它们一起共享cpu时间,当然他们的优先级可能不同,那么获得的cpu时间可能不同,但是操作系统可以保证他们在一个调度周期中最后的虚拟运行时间是一样的,这也是他们名字,完全公平调度的由来,下图是task_tick指向的函数task_tick_fair函数的详解

可以看到它会进行调度标记的设置,在当前进程用完当前时间片或者自己不是运行队列中虚拟运行时间最小的进程时那么就会设置调度标记,从而在时钟中断退出时引发调度。

5.idle_schedule_class 调度类

严格说这个调度类用户进程是不能设置的,因为只有在cpu各个运行队列都没有可运行进程时才会到这个调度类中寻找可以调度的进程,这个进程称为空闲进程,因为它存在的意义就是消耗cpu时间,所以在一些低功耗设备中,一旦调度到这类进程那么cpu可能会设置休眠,反正cpu做的事也没有意义,所以干脆休眠以节省电量,下图为idle_sched_class的task_tick指向的函数task_tick_idle函数源代码

可以看到这个函数是空函数,因为本来中断的是一个“空闲的进程”,因此只能等待高优先级得进程被中断唤醒或者依靠负载均衡把别的进程迁移到本cpu运行队列中,所以如果中断一个idle进程,那么什么也不干,因为系统中只有自己一个进程,没有选择,只能运行自己,所以不会引发调度。

五. schedule 函数概论 

        schedule也称主调度器,它是承担了调度系统的核心功能,它是真正意义上的调度函数,通过它我们把一个进程切换到另一个进程上执行,所以它的重要不言而瑜,这个函数被调用的频率极高,在中断返回,系统调用返回,和进程主动放弃cpu中都能调用到。其实说起schedule函数的功能也可以概括为两个功能,如下所示

(1) 第一就是选核,就是在运行队列中选择下准备一个运行的进程。

(2) 第二就是执行切换,把选取的准备运行的进程替换掉当前进程。

上面这两个步骤可以说是主调度器的核心功能,基本上主调度器都是围绕它展开,第二个部分是和体系结构相关的,因为要处理包括地址空间切换,cpu上下文保存等严重依赖于体系结构的代码,所以会有许多的汇编函数构成,下图为主调度器代码

可以看到其执行的核心函数是_schedule,它调用两个核心函数完成上述两个步骤,

调用 pick_next_task函数完成选择下一个待运行的任务

调用context_switch 函数完成进程切换

自此linux调度子系统的调用框架分析完毕。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值