以上应该来说把调度器相关的内容说的比较明确了,下面我们就来看看函数的具体实现吧。
```void rt_system_scheduler_init(void);
void rt_system_scheduler_start(void);
void rt_schedule(void);
void rt_schedule_insert_thread(struct rt_thread thread);
void rt_schedule_remove_thread(struct rt_thread thread);
void rt_enter_critical(void);
void rt_exit_critical(void);
rt_uint16_t rt_critical_level(void);
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];
if RT_THREAD_PRIORITY_MAX > 32
/ Maximum priority level, 256 /
rt_uint32_t rt_thread_ready_priority_group;
rt_uint8_t rt_thread_ready_table[32];
else
/ Maximum priority level, 32 /
rt_uint32_t rt_thread_ready_priority_group;
endif```
变量作用分析:
rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]
用来存储每个优先级的列表头。
所有函数中关键函数是
void rt_schedule(void);
优先级调度算法就在里面实现,先来看看函数。
```void rt_schedule(void)
{
rt_base_t level;
struct rt_thread to_thread;
struct rt_thread from_thread;
/* disable interrupt */
level = rt_hw_interrupt_disable();
/* check the scheduler is enabled or not */
if (rt_scheduler_lock_nest == 0)
{
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
/* get switch to thread */
to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
/* if the destination thread is not the same as current thread */
if (to_thread != rt_current_thread)
{
rt_current_priority = (rt_uint8_t)highest_ready_priority;
from_thread = rt_current_thread;
rt_current_thread = to_thread;
RT_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread));
/* switch to new thread */
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
(\"[%d]switch to priority#%d \"
\"thread:%.*s(sp:0x%p), \"
\"from thread:%.*s(sp: 0x%p)
\",
rt_interrupt_nest, highest_ready_priority,
RT_NAME_MAX, to_thread->name, to_thread->sp,
RT_NAME_MAX, from_thread->name, from_thread->sp));
ifdef RT_USING_OVERFLOW_CHECK
_rt_scheduler_stack_check(to_thread);
endif
if (rt_interrupt_nest == 0)
{
extern void rt_thread_handle_sig(rt_bool_t clean_state);
rt_hw_context_switch((rt_uint32_t)&from_thread->sp,
(rt_uint32_t)&to_thread->sp);
/* enable interrupt */
rt_hw_interrupt_enable(level);
ifdef RT_USING_SIGNALS
/* check signal status */
rt_thread_handle_sig(RT_TRUE);
endif
}
else
{
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, (\"switch in interrupt
\"));
rt_hw_context_switch_interrupt((rt_uint32_t)&from_thread->sp,
(rt_uint32_t)&to_thread->sp);
/* enable interrupt */
rt_hw_interrupt_enable(level);
}
}
else
{
/* enable interrupt */
rt_hw_interrupt_enable(level);
}
}
else
{
/* enable interrupt */
rt_hw_interrupt_enable(level);
}
}```
这里调度算法有一个变种,当最大优先级大于32时,使用了一个变种算法。这里先来分析最大优先级不大于32的情况。
首先来说,RTT使用的线程调度算法叫做位图调度算法,该算法的好处是,在任意多的优先级以及任意多线程的情况下,都可以在O(1)的时间复杂度情况下完成线程调度(既都为一个固定的时间),这也就为系统的实时性提供了保障。
在这我们先从,最大优先级小于32的情况,来分析整个算法。此时,调度算法实现相关的数据结构有以下几个
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; rt_uint32_t rt_thread_ready_priority_group;
首先是rt_thread_priority_table[RT_THREAD_PRIORITY_MAX],该数组的长度为RT_THREAD_PRIORITY_MAX,它是一个列表数组,功能如开始的图所示,其为每一个处在ready状态,且不同优先级的线程,都创建了一个列表。这个数组内就是各个列表的head地址。
其次来看看rt_thread_ready_priority_group这个变量,该变量为一个32bit的数据,每一个位对应的该优先级是否有ready状态的线程等待调度。举个例子说明:
若rt_thread_ready_priority_group = 0x76543210;则说明:
优先级为4,9,12,13,18,20,22,25,26,28,29,30的线程都已准备就绪。
此时rt_thread_priority_table[4],rt_thread_priority_table[9]….等等列表中就链接着已经准备就绪等待调度的线程。
这里说起来也就很简单了,在rt_thread_ready_priority_group中找出最低的一个非0位,然后在对应的就绪优先级列表中取出线程并调度就好。这里就引出了接下来一个问题如何在rt_thread_ready_priority_group中找出最低的非0位,最简单的,我们可能想出了这么做:
int get_lowest_ready_priority(rt_uint32_t rt_thread_ready_priority_group) { uint8_t i; uint32_t m=1; if(rt_thread_ready_priority_group==0) return -1; for(i=0;i<32;i++) { if(m&rt_thread_ready_priority_group) return i; m<<=1; } }
这个算法虽然简单,但有个问题,可以看到中间个for循环,所以这个算法执行的时间和优先级数量N成正比,时间复杂度为O(N)。那么位图算法是如何解决这个问题的呢?通过空间换时间,引入了一个数组。
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 };
这个数组在rt_thread_ready_priority_group为8bit的时候,也就是最大优先级为8的时候可以立刻得出就绪状态的最小优先级的线程的优先级。
__lowest_bit_bitmap[rt_thread_ready_priority_group]这就是就绪状态优先级最小的线程的优先级。
那么当最大优先级大于8了又如何呢?继续扩大这个矩阵是不可取的,因为,这个矩阵的大小为2^N,当最大优先级提升以后,矩阵将迅速扩大,所占空间变得不可接受。所以使用了另一个函数:
```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;
}```
将32个优先级拆成了4组,先判断每一组是否有就绪的任务,然后再去查表,这样通过2次查表,就完成搜索最低优先级就绪任务列表的功能。将原来O(n)的时间复杂度大幅降低到一个可接受的范围。
但请注意上面的函数严格来说并不能算是O(1)的复杂度,因为当最大优先级为N的时候,上面函数是无法直接写出的,实际上无论分成多少组,仍然是与N成正比关系。比如分4组就是O(N/4)分成3组就是O(N/3)。
分析完最大32个优先级的情况,现在就来看看256个优先级的情况。根据刚才的分析,256个优先级的情况如果按照之前每8位判断一次的方法,那么最坏的情况将需要判断32次,这个时间将不可忽略,所以不能无脑的把int rt_ffs(int value)这个函数扩展。当然因为空间原因,也不能随意扩展const rt_uint8_t lowest_bit_bitmap[]这个数组。
所以针对这种情况,RTT又引入了一个数据:
rt_uint8_t rt_thread_ready_table[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;```
1.先把256个优先级分为32组,每组就是8个,就是一个8bit数据。
2.使用rt_thread_ready_priority_group这个变量的每个位记录这32组中哪一组中有就绪状态的线程队列。
3.使用__rt_ffs()查找出优先级最低的组。
4.再次使用__rt_ffs()查找出该组中优先级最低的就绪队列。
所以除了使用了一次查表,之后随着最大优先级的提升,就是不断的做除法,以减少查找所消耗的时间,把查找所消耗的时间减少到一定的范围之内。
整个RTT线程的核心调度算法就分析完毕了。据说ucos也使用的是类似的算法,没有具体比较过也就不好说到底谁优谁劣,但是该算法既然在256个优先级的情况下,做到了O(1),在不继续增大最大优先级的情况下,其实在改进上其实基本算是到顶了。继续的优化看起来也只能是扩大__lowest_bit_bitmap数组,使用更多的空间来换时间(不划算)。 |