前言
基本信息
名称 | 描述说明 |
---|---|
RT-Thread Studio 软件版本 | 版本: 1.1.3 |
RT-Thread 系统版本 | 4.0.2 |
STM32CubeIDE 软件版本 | 1.4.0 |
STM32芯片型号 | STM32F013VG |
前言说明
前面的分析的是线程调度器的调度流程,下面开始分析RT-thread线程优先级管理的方法。由于在目前RT-thread版本中已经去掉了位图数组,就不再贴出位图数组了。
优先级算法的说明
一个操作系统如果只是具备了高优先级任务能够“立即”获得处理器并得到执行的特点,那么它仍然不算是实时操作系统。因为这个查找最高优先级线程的过程决定了调度时间是否具有确定性,例如一个包含n个就绪任务的系统中,如果仅仅从头找到尾,那么这个时间将直接和n相关,而下一个就绪线程抉择时间的长短将会极大的影响系统的实时性。当所有就绪线程都链接在它们对应的优先级队列中时,抉择过程就将演变为在优先级数组中寻找具有最高优先级线程的非空链表。
RT-Thread内核中采用了基于位图(bitmap)的优先级算法(时间复杂度O(1),即与就绪线程的多少无关),通过位图的定位快速的获得优先级最高的线程。大致来说,就是每次调度的时间是恒定的:无论当前的系统中存在多少个线程,多少个优先级,rt-thread的调度函数总是可以在一个恒定的时间内选择出最高优先级的那个线程来执行。对不同优先级的线程,RT-Thread采用可抢占的方式:即高优先级的线程会“立刻”抢占低优先级的线程。
RT-Thread内核中也允许创建相同优先级的线程。相同优先级的线程采用时间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当前系统中无更高优先级就绪线程存在的情况下才有效。每个线程的时间片大小都可以在初始化或创建这个线程时指定。
优先级算法参数相关的代码使用
参数定义代码片段:
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];//就绪线程优先级链表数组
/* 32位位图变量,当Maximum priority level==32时,可看成一级位图,该变量32bit分别对应32个线程优先级(0-31)*/
rt_uint32_t rt_thread_ready_priority_group;
#if RT_THREAD_PRIORITY_MAX > 32
/* Maximum priority level, 256 */
/* 256位一级位图,代表32个字节,分别对应256个线程优先级。比如第一个字节的bit0表示优先级0,bit7表示优先级7。第二个字节bit0表示优先级8,bit7表示优先级15*/
rt_uint8_t rt_thread_ready_table[32];
#endif
#ifndef RT_USING_SMP
extern volatile rt_uint8_t rt_interrupt_nest;
static rt_int16_t rt_scheduler_lock_nest;
struct rt_thread *rt_current_thread; //保存当前运行的线程(在线程跳转时设置为目标线程to_thread)
rt_uint8_t rt_current_priority; //保存当前运行线程优先级(在线程跳转时设置为目标线程to_thread的优先级)
#endif /*RT_USING_SMP*/
说明:在最大优先级为32时,优先级组的值仅用rt_thread_ready_priority_group表示,而最大优先级在32到256之间的时候新增一个数组rt_thread_ready_table用于32个8位的元素(共有256位)来表示。
线程初始化时参数设置代码片段:
/* priority init */
RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);
thread->init_priority = priority;
thread->current_priority = priority;
thread->number_mask = 0;
#if RT_THREAD_PRIORITY_MAX > 32
thread->number = 0;
thread->high_mask = 0;
#endif
说明:在最高优先级大于32后,新增了number 和high_mask 值,初始值都为0。
线程启动时参数设置代码片段:
/* calculate priority attribute */
#if RT_THREAD_PRIORITY_MAX > 32
thread->number = thread->current_priority >> 3; /* 5bit */
thread->number_mask = 1L << thread->number;
thread->high_mask = 1L << (thread->current_priority & 0x07); /* 3bit */
#else
thread->number_mask = 1L << thread->current_priority;
#endif
线程启动时rt_thread_ready_table设置代码片段:
/* set priority mask */
#if RT_THREAD_PRIORITY_MAX > 32
rt_thread_ready_table[thread->number] |= thread->high_mask;
#endif
rt_thread_ready_priority_group |= thread->number_mask;
说明:
- 在最高优先级值在32时:number_mask 的值为将1左移优先级值大小位。
- 在最高优先级值在256时:number的值为当前优先级的值右移3位;number_mask 的值为将1左移number值大小位;high_mask 的值为将1左移当前优先级值和0x07相且得出的值的位数。举例来说:如果当前线程优先级为200,则number=25(0B11001),number_mask =0B10000000000000000000000000(1后面25个0),high_mask=1。此时执行rt_thread_ready_table[thread->number] |= thread->high_mask;相当于将rt_thread_ready_table第25个元素的值或上thread->high_mask的值1。rt_thread_ready_priority_group 的值为当前值或上number_mask的值。
小结说明:
- 根据上述代码内容,我们可以看到当最高优先级为32位时,优先级组的值中二进制中1的位置编号即表示当前优先级列表的编号值。
- 而当最高优先级为256时,新增了一个rt_thread_ready_table数组来表示优先级链表的位置编号,这个数组固定大小元素个数为32个,每个元素长度为8位,共有256位。把rt_thread_ready_table数组整个内容看做优先级组列表时。
- 例如当线程优先级为200时,将200除以8则可以得出当前200应该在rt_thread_ready_table数组的元素位置号。对应的代是:
thread->number = thread->current_priority >> 3;
而原来的rt_thread_ready_priority_group变量表示的就是当前优先级组在rt_thread_ready_table数组的元素位置编号。对应的代码是:
thread->number_mask = 1L << thread->number;
rt_thread_ready_priority_group |= thread->number_mask;
这两段代码表示就是200这个值在rt_thread_ready_table数组中存储位置的元素编号。而具体存在这个元素的哪一位呢?按照文字描述应该为当前优先级的值除以8之后取余数,将1左移这个获取的余数就是其在rt_thread_ready_table数组中对应元素中,bit位的位置。例如200则左移的位数为0,201左移位数为1。而 (thread->current_priority & 0x07)这个操作就是对当前优先级值除以8,取余数值。接下来的代码就是将这个位值赋值给rt_thread_ready_table数组对应元素。对应的看下面的代码:
thread->high_mask = 1L << (thread->current_priority & 0x07); /* 3bit */
rt_thread_ready_table[thread->number] |= thread->high_mask;
通过上面的描述可以了解当前线程优先级组的存储位置:
- 最高优先级32时,存在rt_thread_ready_priority_group 变量中。
- 最高优先级256时,存在rt_thread_ready_table数组中,而使用rt_thread_ready_priority_group 变量来记录在数组中对应的元素位置。
前面是线程还没正式被调度器调度之前优先级相关参数值的设置。
在调度时使用的代码片段(_get_highest_priority_thread函数使用):
下面是_get_highest_priority_thread部分代码:
#if RT_THREAD_PRIORITY_MAX > 32
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;
#else//获取当前最高就绪的优先级组编号
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#endif
说明:这个代码的关键就是得出,当前线程优先级组的编号
- 最高优先级32时,直接使用__rt_ffs根据rt_thread_ready_priority_group的值,得出rt_thread_ready_priority_group变量值中从右边数最近的bit位1,位于数字的第几位。就得出优先级组的编号。
- 最高优先级256时,使用__rt_ffs根据rt_thread_ready_priority_group的值得出在数组中优先级元素的编号。再根据这个编号的值得出对应数组中的元素值,再用__rt_ffs求出对应元素中从右边数最近的bit位1,位于数字的第几位,此时相当于除去了余数(原来优先级值除以8的余数),加上位置编号的值乘以8(就得出了原来优先级值除以8 的整数),最终加起来的结果就是优先级组编号的值。
可以看出上述的两种求优先级组编号的值的方式代码量差异不大,因此无论是最高优先级是32还是256,在进行线程调度的时候的优先级计算的时间差异很小,这就保证了:
无论当前的系统中存在多少个线程,多少个优先级,rt-thread的调度函数总是可以在一个恒定的时间内选择出最高优先级的那个线程来执行。
总结
这是大致的流程图
根据上面的描述,对于线程优先级算法,在最高优先级数量不同的情况下,我个人认为这种差异,只是查找线程在优先级链表中对应位置编号的方式上的差异。