概述
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))
将此线程插入到末尾,切换到表头线程运行。
对于不同优先级,采取抢占式调度,每次会从优先级链表找出当前最高优先级链表(即非空),然后在其中取出第一个线程运行。
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,无最高优先级 |
0x01 | bit0 = 1,最高优先级为0 |
0x02 | bit1 = 1,最高优先级为1 |
0x03 | bit0 = 1,最高优先级为0 |
0x04 | bit2 = 1,最高优先级为2 |
… | |
0xfe | bit1 = 1,最高优先级为1 |
0xff | bit0 = 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 位图算法 所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复