源码解读·RT-Thread多任务调度算法

*本文依据RT-Thread当时最新版本4.0.1版本源码算法

RT-Thread操做系统是一款基于优先级和时间片轮转的多任务实时操做系统。其调度算法采用256个优先级,并支持相同优先级的任务存在。不一样优先级的任务采用优先级调度,而相同优先级的任务则采用时间片轮转调度。其实这种调度算法在绝大多数系统中都同样,像我知道的μCos和freertos都是如此。不过这里须要先了解一个问题,也是我初学时被困扰的问题——多种调度算法存在时那么什么时候采用何种调度算法?彼此又是如何共存和协调进行的?这要等看完并看懂调度算法的源码以后才算明白其中原理。其实调度算法采用优先级调度为主要依据,以时间片轮转为次要依据。也就是说只当没有更高优先级任务就绪的状况下,想同优先级任务之间的调度才会采用时间片轮转调度。数组

优先级调度

优先级在多任务调度中是什么?优先级实际上是给任务分配的一个数值,数值越小则优先级越高。优先级的高低将直接反应在任务调度算法中,优先级越高越优先响应。函数

在RT-Thread中优先级调度算法支持256个优先级,能够经过宏定义配置。一般状况下裁剪过程当中会根据须要来定义优先级数量。不过在源码中只会体现出两种不一样优先级数量的差别,分别为32个和32个以上。RT-Thread采用bitmap算法来计算优先级。bitmap算法是二进制与位运算完美结合的体现。看懂代码以后相信你们都会来一句“卧槽,既然还能够这样操做!”,真的崇拜发明bitmap算法的大佬,不过我并不知道是谁最早发明的,第一次接触是在学习μCos的时候。学习

前面提到过,RT-Thread根据优先级的数量不一样分为两种bitmap算法(优先级32个和32个以上),代码稍微有些差别,主要是为了优化资源占用。其中不超过32个优先级的状况下只会用一个32bit的变量,超过32个优先级后会使用一个长度为32个元素的byte数组,外加一个32bit的变量用来分组。其中不管多少个优先级,每一个优先级都只须要用一个bit来表示对应优先级的任务是否就绪状态(为1表示就绪,为0表示挂起),因此最多支持256个优先级。优化

bitmap算法

为了理解优先级计算中使用的bitmap算法,首先必需要先掌握十进制与二进制的转换,而且还须要掌握位运算。以RT-Thread中优先级大于32个的状况为例来讲明,其32个及一下优先级数量的方式更简单,稍后会简单说明。先看RT-Thread中的源码,关于bitmap算法会用到的几个变量:ui

rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];
struct rt_thread *rt_current_thread;
rt_uint8_t rt_current_priority;

#if RT_THREAD_PRIORITY_MAX > 32
    /* Maximum priority level, 256 */
    rt_uint32_t rt_thread_ready_priority_group;
    rt_uint8_trt_thread_ready_table[32];
#else
    /* Maximum priority level, 32 */
    rt_uint32_t rt_thread_ready_priority_group;
#endif

 

RT-Thread中当优先级大于32个的状况下,将任务优先级分为32组,每组8个。其分组用rt_thread_ready_priority_group变量来管理,这是一个32bit的变量,每个bit表明一个组的就绪态。而每一组中的8个优先级则用rt_thread_ready_table数组来管理,其每一个元素占用一个8bit大小,一样每一个bit表明一个优先级。在源码中优先级从0开始最大到255,因此具体的分组状况就是每8个为一组用一个bit来表示,依次分为32组共须要32个bit来表示分组,也就是rt_thread_ready_priority_group变量。当某一组中对应的优先级任务处于就绪态则对应的bit将被置1.例如优先级为19的任务处于就绪,则根据分组状况其处于第三组(16至23),因此rt_thread_ready_priority_group中的第三bit将置1(第三个bit也就是bit2,由于一般习惯将bit从0开始数).这就在后续的调度算法过程当中调度器知道此组中有任务处于就绪了。更进一步的细节是,还须要在rt_thread_ready_table中第三个元素(下标为2)中的第四bit(bit3)置1(由于19在第三组16,17,18,19中位于第四个).这样就准确的标识了惟一的优先级号。spa

那若是同时有多个组中的任务都处于就绪该怎么计算一个最高优先级呢?若是存在同时处于就绪的任务则对应的分组bit都会在rt_thread_ready_priority_group中置1,且同时在rt_thread_ready_table中对应的组中的bit也会置1.前面提到过优先级越高其优先级编号越小,好比优先级0是最高的优先级,优先级255是最低优先级。这就能够推理得知越小的优先级编号就对应在rt_thread_ready_priority_group越低的bit上。不难看出0至7优先级对应rt_thread_ready_priority_group变量的bit0,同理8至15对应比bit2,16至23对应bit3.等等。现假设优先级5和19的任务处于就绪态,那么其rt_thread_ready_priority_group变量的bit0和bit2将被置1,同时rt_thread_ready_table[0]的bit5以及rt_thread_ready_table[2]的bit3将被置1.以下图所示: 操作系统

 

 

 

若是此时调度器调度时,应该要计算出优先级为5的任务来执行。这其实分了三步来计算的。code

首先拿rt_thread_ready_priority_group变量利用ffs函数计算最低位为1的bit是第几个bit。显然这个例子中是第一个bit0位为1,假如这个结果咱们用叫index的变量暂存起来,那么index等于1.blog

第二步利用第一步计算的结果index做为rt_thread_ready_table的索引(索引从0开始为第一个,因此要index-1),即rt_thread_ready_table[0]。再一次作ffs(rt_thread_ready_table[0])计算最低位为1的bit是第几位,显然例子是bit5,假如咱们再用个变量offset存储这个值,那么offset等于6。

第三步根据前两步计算出来的index和offset得出最终的最高优先级为(index-1)*8+(offset-1),这里的乘法能够用位运算代替,因此等价于((index-1) <<3) + (offset-1).其真正的RT-Thread源码以下:

register rt_ubase_t highest_ready_priority;

#if RT_THREAD_PRIORITY_MAX <= 32
    highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;

#else
    register rt_ubase_t number;

    number = __rt_ffs(rt_thread_ready_priority_group) -1;
    highest_ready_priority = (number << 3) +__rt_ffs(rt_thread_ready_table[number]) - 1;
#endif

 

其上面的代码__rt_ffs返回值就如上例的index变量,作了减1操做是由于索引和bit都从0开始。highest_ready_priority即计算出来的最高优先级。

关于ffs函数的实现细节能够看RT-Thread源码里的各类实现方式,有C函数实现,也有针对各类编译器和处理器优化的特殊指令的实现。不过其功能就是计算一个值二进制位为1的最低位是第几位。在RT-Thread的C函数实现中作了一个0到255的索引数组,其数组的值分别就是0到255这些数值所对应的二进制位为1的最低位索引。

最后说明一下,如上面的代码所示,当优先级数定义为不超过32个时,就不存在rt_thread_ready_table了,更节省资源。也能够理解为每组只有一个优先级,因此能够直接用rt_thread_ready_priority_group直接代替了。由于最多才32个优先级,rt_thread_ready_priority_group恰好32bit,每一个bit表明一个优先级恰好对应上。

时间片轮转调度

在说明时间片轮转调度前,先要说明一下什么是时间片。在操做系统里,时间片的概念是相对于操做系统的TICK中断的。每触发一次TICK中断就至关于一个时间片。

时间片轮转调度会在每一个TICK中断时对当前任务的时间片减一,而后检查其它任务的时间片剩余状况。一旦当前任务的时间片用完,则会先重置当前任务的时间片。而后看是否有想同优先级的任务,若是有则会将当前任务移到队列末尾。而后触发优先级调度,此时只要当前优先级是已就绪的最高优先级最终就会取出相同优先级队列头的任务运行。抛开其它因素简单来讲就是只要当前任务的时间片用完了,则会将当前任务移到队列末尾,下一个任务天然而然处于队列头将得到运行。因此这就看起来是每一个任务轮流来运行,只是每一个任务的运行时间长短不同而已,这个运行的时间长短就是由时间片指定的。

综上所述,体现时间片轮转调度的前提是创建多个相同优先级的任务。由于时间片轮转调度只会发生在相同优先级的任务之间。不然能够认为系统中只存在优先级调度。

下图展现了三个相同优先级任务的时间片轮转调度运行状况:

 

相关文章
相关标签/搜索