我是靠谱客的博主 大力野狼,这篇文章主要介绍[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):

复制代码
1
2
3
4
5
6
7
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字节,因此引入:

复制代码
1
2
rt_uint8_t rt_thread_ready_table[32];

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

复制代码
1
2
3
4
5
6
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):

复制代码
1
2
3
4
5
6
7
8
9
10
11
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

最终可得到如下的表:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* * 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如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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开始):

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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函数:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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中:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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指令用法
复制代码
1
2
3
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位:

复制代码
1
2
3
4
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位:

复制代码
1
2
3
4
__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]内容请搜索靠谱客的其他文章。

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

评论列表共有 0 条评论

立即
投稿
返回
顶部