Linux Scheduler之rt选核流程

前言

在Linux中,有些线程需要被公平调度,保证每个线程不会长时间的调度不到,这就是我们熟知的CFS调度类(sched class),但是也有一些关键线程(比如一些显示刷帧的支撑线程),我们需要保证线程能够及时被调度到,针对普通负载较轻的场景,线程的调度及时性都能得到保证。

但是为了满足人们的日常使用需求,操作系统后台驻留任务越来越多(这种现象在Android设备上表现尤为严重),而系统的CPU始终只有一个,即使这个CPU有8个核甚至更多,CPU也有可能被塞满task,为了保证一些重要task的及时运行,这里就有了实时进程的概念。

Linux中,系统是通过优先级来区分非实时线程和实时线程的,Linux将线程优先级分为140个等级,从0~139,这个值越小其优先级越高,实时线程也叫rt(real time)线程,其优先级范围为[0,99],非实时线程为[100,139]。

非实时线程也叫cfs线程,他的default优先级为120,通过nice值转化为最终的线程优先级。Nice值的取值范围为-20~19,可以通过ps命令查看NI列:

图片[1]-Linux Scheduler之rt选核流程-不念博客
rt选核流程

本文主要讲述rt线程的选核流程。

rt选核流程介绍

每个task在被wakup唤醒时候都会从try_to_wake_up开始执行,这里会为task选择合适的CPU运行,其核心逻辑位于select_task_rq函数里面,它会根据task的sched_class确定调用的具体函数,而task的sched_class初始化则是在系统更改或者设置task的priority时,根据task的priority进行设置:

图片[2]-Linux Scheduler之rt选核流程-不念博客
rt选核流程

如果一个线程为rt,它的sched_class则为rt_sched_class,结构体初始化定义如下:

图片[3]-Linux Scheduler之rt选核流程-不念博客
rt选核流程

那么rt线程对应调度类的选核函数为select_task_rq_rt,其基本的执行逻辑如下图1:

图片[4]-Linux Scheduler之rt选核流程-不念博客
图1

rt选核流程比较简单,其核心逻辑位于find_lowest_rq函数中,此函数主要用于寻找符合当前rt线程运行的cpu,其核心逻辑如下图2:

图片[5]-Linux Scheduler之rt选核流程-不念博客
图2

cpupri_find_fitness负责从所有系统中所有的符合task运行条件的cpu找出来,并更新到lowest_mask里面,然后find_lowest_rq再从最终的lowest_mask里面选择合适的CPU,选择逻辑:

  1. 如果lowest_mask里面包含task的prev cpu,则直接选择prev cpu。
  2. 选择lowest_mask在task的sched domain里面的第一个cpu。
  3. 如果前面都没找到CPU,则判断lowest_mask里面是否包含了wake cpu,如果包含则直接返回wake cpu
  4. 最后如果都没找到,lowest又不为空,则从lowest_mask里面找一个随机的cpu返回。

下面来看下cpupri_find_fitness如何找到合适的所有适合task运行的cpu,主要分为3个部分:

图片[6]-Linux Scheduler之rt选核流程-不念博客
rt选核流程

1、首先对task优先级进行一个转化,将系统中的task分为0~102个等级,优先级由低到高,可以分为invalid,idle,normal和rt四类。

图片[7]-Linux Scheduler之rt选核流程-不念博客
图3

其中invalid优先级为0,idle优先级为1,所有的cfs线程占用一类,优先级为2,rt则每个优先级占用一个等级。

2、因为rt线程是可以抢占的,for循环从最低优先级开始遍历,这里的优先级为已经转化为103个级别的优先级状态,其选核逻辑一般为首先选择idle的cpu运行,其次是抢占有cfs task的cpu运行,最后才会考虑取抢占其他低优先级rt task的cpu,通过__cpupri_find查找各个优先级在cpu上的运行状态,找到可以使用的cpu。

cpupri_vec结构体有两个成员,count用来存储各个优先级task在哪些CPU上属于最高优先级task,mask则用来存储当前优先级所在CPU上是最高优先级的的cpumask。

例如当前系统中cpu0~5上没有全是cfs task在运行,那么normal task的count值为6,mask为0x3f。

图片[8]-Linux Scheduler之rt选核流程-不念博客
rt选核流程

__cpupri_find的基本逻辑:

  1. 判断当前优先级在哪些CPU上是最高优先级,如果没有的话,说明当前系统要么没有此优先级task,要么是当前优先级task并非系统中各个CPU的最高优先级。
  2. 当前优先级task在有cpu是处于最高优先级,从这些cpu里面去除掉task not allowed cpu,再去除掉被isolation的cpu,最后如果lowest_mask还有CPU,则将lowest_mask作为后面选核的基础。

经过__cpupri_find找到lowest_mask后,再从lowest_mask里面过滤掉capacity无法满足当前task的cpu,最后剩下的就是可以用来运行当前task的cpumask,如果没有剩余,则说明当前优先级的task所运行的cpu没有满足条件的,此时需要进入下一个循环,找到更高优先级的task运行的cpu。

3.如果此次循环没有找到合适的cpu,会再进行一次循环,尝试找到合适的CPU。

至此,rt选核的整体流程介绍完毕。

结语

本文主要从代码的角度讲述了Linux rt选核的主要流程,旨在让读者对于rt线程及其选核逻辑有一个初步的认识,利用rt线程的优点,可以解决当前系统中因为调度延迟引起的一些性能问题。

rt线程选核相对cfs线程而言要简单一些,他不会考虑energy等因素,但系统中过多的rt task可能会带来一定的功耗影响,同时由于rt主要是为了解决重要task的调度延迟问题,如果系统中过多的rt线程可能导致一些重负载场景可能所有CPU都是rt task,引发rt线程的调度延迟,从而导致更严重的性能问题,所以也不宜在系统中设置过多的rt task。

© 版权声明
THE END