初始
- 在RT-Thread的启动流程中,rtthread_startup的最后一步,调用了rt_system_scheduler_start开启了线程调度器,此函数源码如下:
void rt_system_scheduler_start(void)
{
register struct rt_thread *to_thread;
register rt_ubase_t highest_ready_priority;
#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
to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
rt_current_thread = to_thread;
rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);
}
- 代码中,无论RT_THREAD_PRIORITY_MAX设置的最大优先级是否大于32,都会调用__rt_ffs从就绪线程的优先级分组中选择当前处于就绪态并且优先级最高的线程索引,关于__rt_ffs函数剖析见此处
- 这里若最大线程数不超过32,即最大线程优先级不超过32,则会用一个32bit的变量,其中每一个bit表示每一个线程的状态,若线程为就绪态则为1,挂起态则为0;反之,会使用一个长度为32且每个元素为8bit变量的数组以及一个32bit变量用来分组。支持最大线程数为256。
#if RT_THREAD_PRIORITY_MAX > 32
rt_uint32_t rt_thread_ready_priority_group;
rt_uint8_t rt_thread_ready_table[32];
#else
rt_uint32_t rt_thread_ready_priority_group;
#endif
运行
void rt_schedule(void)
{
rt_base_t level;
struct rt_thread *to_thread;
struct rt_thread *from_thread;
level = rt_hw_interrupt_disable();
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
to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
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));
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
("[%d]switch to priority#%d "
"thread:%.*s(sp:0x%p), "
"from thread:%.*s(sp: 0x%p)\n",
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)
{
rt_hw_context_switch((rt_uint32_t)&from_thread->sp,
(rt_uint32_t)&to_thread->sp);
#ifdef RT_USING_SIGNALS
if (rt_current_thread->stat & RT_THREAD_STAT_SIGNAL_PENDING)
{
extern void rt_thread_handle_sig(rt_bool_t clean_state);
rt_current_thread->stat &= ~RT_THREAD_STAT_SIGNAL_PENDING;
rt_hw_interrupt_enable(level);
rt_thread_handle_sig(RT_TRUE);
}
else
#endif
{
rt_hw_interrupt_enable(level);
}
return ;
}
else
{
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));
rt_hw_context_switch_interrupt((rt_uint32_t)&from_thread->sp,
(rt_uint32_t)&to_thread->sp);
}
}
}
rt_hw_interrupt_enable(level);
}
- 失能全局中断
- 判断当前是否处于临界区中,若处于临界区,则不进行任何操作,使能全局中断后退出。
- 若未处于临界区,则先执行调度钩子函数,之后搜索出已就绪最高优先级的线程,若此线程为当前正在执行的线程,则不进行任何操作,使能全局中断后退出。反之,则进行一次上下文切换,这里,若未处于中断中,除了执行一次上下文切换外,若使能了信号且当前线程处于等待信号的状态,则会进行一次信号处理。
- rt_schedule_insert_thread 实现如下:
void rt_schedule_insert_thread(struct rt_thread *thread)
{
register rt_base_t temp;
RT_ASSERT(thread != RT_NULL);
temp = rt_hw_interrupt_disable();
thread->stat = RT_THREAD_READY | (thread->stat & ~RT_THREAD_STAT_MASK);
rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),
&(thread->tlist));
#if RT_THREAD_PRIORITY_MAX <= 32
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("insert thread[%.*s], the priority: %d\n",
RT_NAME_MAX, thread->name, thread->current_priority));
#else
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
("insert thread[%.*s], the priority: %d 0x%x %d\n",
RT_NAME_MAX,
thread->name,
thread->number,
thread->number_mask,
thread->high_mask));
#endif
#if RT_THREAD_PRIORITY_MAX > 32
rt_thread_ready_table[thread->number] |= thread->high_mask;
#endif
rt_thread_ready_priority_group |= thread->number_mask;
rt_hw_interrupt_enable(temp);
}
- 此函数主要用于将线程插入于就绪列表中,并且更新其状态为就绪态以及更新线程就绪位变量,便于下一次已就绪最高优先级线程索引的获取。
- rt_schedule_remove_thread 此函数执行功能与上述相反,此处不做详细说明,总而言之,除了对就绪线程列表的更新,就是对线程就绪位变量的更新。
临界区操作
- 临界区操作,个人理解是对调度器的锁控制,若进入临界区,则表示在退出临界区前无法执行线程调度。临界区操作,包括rt_enter_critical、rt_exit_critical及rt_critical_level。这里使用方法同rt_interrupt_enter与rt_interrupt_leave以及rt_hw_interrupt_enable与rt_hw_interrupt_disable。rt_enter_critical、rt_exit_critical的调用在代码中必须成对出现,否则会导致调度器锁死。
- rt_enter_critical函数实现如下:
void rt_enter_critical(void)
{
register rt_base_t level;
level = rt_hw_interrupt_disable();
rt_scheduler_lock_nest ++;
rt_hw_interrupt_enable(level);
}
void rt_exit_critical(void)
{
register rt_base_t level;
level = rt_hw_interrupt_disable();
rt_scheduler_lock_nest --;
if (rt_scheduler_lock_nest <= 0)
{
rt_scheduler_lock_nest = 0;
rt_hw_interrupt_enable(level);
if (rt_current_thread)
{
rt_schedule();
}
}
else
{
rt_hw_interrupt_enable(level);
}
}
- 这里在rt_scheduler_lock_nest为0时,会对当前线程是否存在进行判断,若不存在,则表示当前可能处于调度器初始化状态,不执行调度操作。
- rt_critical_level 通过此函数能够获取当前进入临界区的嵌套数。
- 另外,在执行中断服务时会需要调用rt_interrupt_enter函数,此函数支持嵌套,执行它表示当前存在中断服务执行,此时即使没有进入临界区,也会将调度器上锁;在完成中断服务时会需要调用rt_interrupt_leave函数,若不调用此函数,将不会启动线程调度。