我是靠谱客的博主 大力野狼,最近开发中收集的这篇文章主要介绍[019] [RT-Thread学习笔记] 线程位图调度算法 1 线程优先级链表 2 位图算法 ,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

RT-Thread
学习笔记
线程优先级链表
位图算法

RT-Thread版本:4.0.5
MCU型号:STM32F103RCT6(ARM Cortex-M3 内核)

1 线程优先级链表

每个线程控制块都带有一个链表成员,根据优先级将thread->slist插入对相应优先级链表中,对于相同优先级采取时间片轮转调度方式,若线程当前时间片已用完,且其所在的优先级队列为当前系统最高优先级,则调用rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),&(thread->tlist))将此线程插入到末尾,切换到表头线程运行。
image-20220326005605388

对于不同优先级,采取抢占式调度,每次会从优先级链表找出当前最高优先级链表(即非空),然后在其中取出第一个线程运行。
image-20220326005734598

2 位图算法

查找最高优先级的线程,首先得先找到当前最高的就绪优先级,最容易想到的调度算法(调度算法1):

    for(i = 0; i < 256; i++)
    {
        if(rt_thread_priority_table[i] != NULL)
            break;
    }
    highest_ready_priority = i;    

此算法可以找到当前最高的就绪优先级,但当最高优先级为255时,每次都要遍历完优先级表才能找到,时间复杂度O(n)

每个优先级链表是否存在线程,可以用一个bit位来表示,对于256级的线程,则共需要256个bit位,即32字节,因此引入:

rt_uint8_t rt_thread_ready_table[32];

数组的第一个字节的bit0表示优先级0,bit7表示优先级7,第而个字节的bit0表示优先级8,以此类推:

bit:    7   6   5   4   3   2   1   0
byte0 |007|006|005|004|003|002|001|000|
byte1 |0l5|014|013|012|011|010|009|008|
.................................
byte31|255|254|253|252|251|250|249|248|

这32个字节所组成的256个bit,他们的排列方式很像一张图(map),所以这种方式就别称为位图(bit map)

如创建了一个优先级为125的线程,并且加入到就绪链表中,则将字节 15 (125 / 8)的bit5(125 % 8)置1,即rt_thread_ready_table[125/8].BIT5 = 1

此时遍历rt_thread_ready_table[32]数组每个bit位即可找到最高就绪优先级(调度算法2):

for(i = 0; i < 32; i++)
{
    for(j = 0; j < 8; j++)
    {
        if (rt_thread_priority_table[i] & (1<<j) ) 
            break;//这就是找到最低的那个为1的bit位置了。
    }
    //下面就是我们找到的最高优先级
    highest_ready_priority = i * 8 + j;
}

对于此算法,若BIT0为1,则执行一次即跳出循环,如果BIT0-BIT254都是0,仅BIT255为1,则循环256次。 平均循环次数32*8/2次,时间复杂度依然为O(n)

现在将位图看作一个变量,并假定当前优先级范围为0~7,则位图变量可以用一个字节表示。当bit0为1时,最高优先级为0,当位图变量为255时,最高优先级也为0(此时bit0还是为1),因此当位图变量取0-255之间的任意一个数字时,它的最低为1的BIT位置都是预知的(对应最高优先级)。

我们可以预先将位图变量的所有取值所对应的最高优先级计算出来,并存成一张表格,然后就可以避免算法2中的for循环,而只需要查表即可,用空间换取时间。

例如,8位位图变量所有可能取值对应的最高优先级:

位图变量取值最高优先级
0x00均为0,无最高优先级
0x01bit0 = 1,最高优先级为0
0x02bit1 = 1,最高优先级为1
0x03bit0 = 1,最高优先级为0
0x04bit2 = 1,最高优先级为2
0xfebit1 = 1,最高优先级为1
0xffbit0 = 1,最高优先级为0

最终可得到如下的表:

const rt_uint8_t __lowest_bit_bitmap[] =
{
    /* 00 */ 0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 10 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 20 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 30 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 40 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 50 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 60 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 70 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 80 */ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 90 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* A0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* B0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* C0 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* D0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* E0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* F0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};

对于0x00其值虽为0,但不代表最高优先级就为0。

当优先级为0~7时,可直接查表找到当前最高的优先级,如当前线程优先级为1/3/5,则bit1、bit3、bit5置1,位图变量取值为0x2A,查表__lowest_bit_bitmap[0x2A] = 1,即可判断当前最高优先级为1。

8优先级可以绘制2^8字节表格来解决,如果32优先级,则需绘制2^32 = 4G字节表格,显然这是不可接受的。

32个优先级,即优先级位图变量可以使用u32型,即变量rt_thread_ready_priority_group,对这4个字节从字节0开始依次查表,如果字节0中非0,则最高优先级一定存在于字节0中,我们对字节0查表rt_lowest_bitmap,即可以得到最高优先级。 如果字节0为0,字节1非0,我们对字节1查表得到的是字节1中为1的最低bit位,然后加上8,就是系统的最高优先级,其他字节同理。调度算法3

   /*
     * rt_thread_priority_bitmap 用来表示当前系统优先级位图。
     * highest_ready_priority表示当前系统中最高优先级
     */
    if (rt_thread_priority_bitmap & 0xff)
    {
        highest_ready_priority = rt_lowest_bitmap[rt_thread_priority_bitmap & 0xff];
    }
    else if (rt_thread_priority_bitmap & 0xff00)
    {
        highest_ready_priority = rt_lowest_bitmap[(rt_thread_priority_bitmap >> 8) & 0xff] + 8;
    }
    else if (rt_thread_priority_bitmap & 0xff0000)
    {
        highest_ready_priority = rt_lowest_bitmap[(rt_thread_priority_bitmap >> 16) & 0xff] + 16;
    }
    else
    {
        highest_ready_priority = rt_lowest_bitmap[(rt_thread_priority_bitmap >> 24) & 0xff] + 24;
    }

32优先级位图查表算法rt-thread如下:

int __rt_ffs(int value)
{
    if (value == 0) return 0;

    if (value & 0xff)
        return __lowest_bit_bitmap[value & 0xff] + 1;

    if (value & 0xff00)
        return __lowest_bit_bitmap[(value & 0xff00) >> 8] + 9;

    if (value & 0xff0000)
        return __lowest_bit_bitmap[(value & 0xff0000) >> 16] + 17;

    return __lowest_bit_bitmap[(value & 0xff000000) >> 24] + 25;
}

rt-thread位图变量最低有效位从1开始,因此相对调度算法3多+1。

当前系统支持的线程优先级为256时,可以用位图数组rt_uint8_t rt_thread_ready_table[32]来表示优先级,此时采用算法3思路,可对这32个字节依次查表,但为了提升系统实时调度的性能,我们需要对算法3进行改进,即使用二级位图

所谓二级位图,即先确定32个字节中最低的非0的字节,因此需要对这32个字节引入一个32个bit的位图变量,每一个bit位表示对应的字节是否为0。rt-thread采用的变量名为rt_thread_ready_priority_group(与32位优先级调度算法中的位图变量同名)

根据上面的分析,要想使用这个二级位图算法,rtt在跟踪线程的状态转换时,不仅需要维护256bit的位图变量数组rt_thread_ready_table[thread->number] |= thread->high_mask,还需要维护32bit的 字节位图变量 rt_thread_ready_priority_group。参看线程启动函数和插入调度就绪链表函数的代码(rt-thread位图变量最低有效位从1开始):

rt_err_t rt_thread_startup(rt_thread_t thread)
{
    [...]
    /* set current priority to init priority */
    thread->current_priority = thread->init_priority;
    
#if RT_THREAD_PRIORITY_MAX > 32
(1) thread->number      = thread->current_priority >> 3;            /* 5bit */
(2) thread->number_mask = 1 << thread->number;
(3) thread->high_mask   = 1 << (thread->current_priority & 0x07);   /* 3bit */
#else
(4) thread->number_mask = 1 << thread->current_priority;
#endif
    [...]
}
 
void rt_schedule_insert_thread(struct rt_thread *thread) 
{
    [...]
#if RT_THREAD_PRIORITY_MAX > 32
(5) rt_thread_ready_table[thread->number] |= thread->high_mask;
#endif
(6) rt_thread_ready_priority_group |= thread->number_mask;
    [...]
}

32优先级:

  • (4) 将thread->number_mask与当前优先级相对应的位置1
  • (6)修改位图变量rt_thread_ready_priority_group对应的位,方便后面直接查表__lowest_bit_bitmap

256优先级:

  • (1) 右移3位相当于将当前线程的优先级/ 2^3,因此thread->number表示当前这个优先级对应的位图32个字节中的第几个字节(number范围0~31)
  • (2)thread->number_mask为thread->number的掩码,方便后续位或/位与操作
  • (3)thread->current_priority & 0x07即thread->current_priority%8, thread->high_mask表示该优先级在上面字节中的第几个bit,例如bit4,则thread->high_mask = 1<<4,这样同样方便后续位或/位与操作
  • (5)将当前优先级所在的字节对应的位置位

rtt首先确定32个字节的位图中,非0的最低的那个字节,然后再查表得到这个字节非0的最低那个bit。这两步骤正好可以利用两次上面的表格rt_lowest_bitmap。

最终的rt_schedule函数:

void rt_schedule(void)
{
    [...]
#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
    [...]
}

-1是因为在__lowest_bit_bitmap中,rt-thread位图变量最低有效位从1开始,但最高优先级是从0开始的

最终实现的调度算法时间复杂度为O(1)

可以看出位图调度算法的核心就是查找字节最低非0 bit位查表法软件实现,是整个位图调度算法的核心。ARM公司提供专门的指令获取寄存器最低位,只要几条汇编语句就可以完成同样的功能,而且性能更好,cortex-m3中:

// libcpu/arm/cortex-m3/cpuport.c
__asm int __rt_ffs(int value)
{
    CMP     r0, #0x00
    BEQ     exit

    RBIT    r0, r0			; 位反转(把一个32位整数用2进制表达后,再旋转180度)
    CLZ     r0, r0			; 计算前导零的数目
    ADDS    r0, r0, #0x01

exit
    BX      lr
}
  • RBIT指令用法
LDR		R1, =0xB4E10C23	; R1 = 1011,0100,1110,0001,0000,1100,0010,0011
RBIT	R0, R1			; R0 = 1100,0100,0011,0000,1000,0111,0010,1101

确实是水平旋转180°。

举例:value = 0xf200

汇编实现的查找最低非0 bit位:

RBIT    r0, r0			; 1111,0010,0000,0000 -> 0000, 0000, 0100, 1111 
CLZ     r0, r0			; 前导零数目r0 = 9, 即value 最低非0 bit位为bit9  (从0开始)
ADDS    r0, r0, #0x01	

位图查表法实现的查找最低非0 bit位:

__lowest_bit_bitmap[(0xf200 & 0xff00) >> 8] + 9;
__lowest_bit_bitmap[0xf2] + 9;
1+9 = 10	// 从1开始

两者都能找到最低非0 bit位,但显然汇编法时间复杂度和空间复杂度都优于位图查表法,但需要内核支持这些汇编指令。

参考:

  • RT-Thread的内核调度算法
  • rt-thread的位图调度算法分析

END

最后

以上就是大力野狼为你收集整理的[019] [RT-Thread学习笔记] 线程位图调度算法 1 线程优先级链表 2 位图算法 的全部内容,希望文章能够帮你解决[019] [RT-Thread学习笔记] 线程位图调度算法 1 线程优先级链表 2 位图算法 所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(79)

评论列表共有 0 条评论

立即
投稿
返回
顶部