线程调度
文章目录
前言
RT-Thread 采用基于优先级的抢占式线程调度,即总是保证当前运行的是优先级最高的就绪线程。在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的。对处于同一优先级下的线程采用时间片轮转调度。
在 RT-Thread 中,会采用一个数组来实现线程优先级管理系统。
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];
这是一个队列数组,最大可支持 256 个队列数组,如果系统最大支持32个优先级,那么这里将是32个优先级队列数组。每个优先级队列采用双向环形链表的方式链接,一般只包含一个idle线程。其结构示意图如下:
调度器初始化
我们从调度器的初始化开始分析,下面是 rt_system_scheduler_init 函数源码( 去掉多余定义后 ):
void rt_system_scheduler_init(void)
{
register rt_base_t offset;
rt_scheduler_lock_nest = 0;//调度器嵌套锁计数器设为0
/* 输出日志信息 */
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("start scheduler: max priority 0x%02x\n",
RT_THREAD_PRIORITY_MAX));
/* 所有优先级对应的队列数组初始化 */
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
{
rt_list_init(&rt_thread_priority_table[offset]);
}
/* 全局变量 rt_thread_ready_priority_group 初始化为 0 */
rt_thread_ready_priority_group = 0;
#if RT_THREAD_PRIORITY_MAX > 32
/* 初始化就绪链表 */
rt_memset(rt_thread_ready_table, 0, sizeof(rt_thread_ready_table));
#endif /* RT_THREAD_PRIORITY_MAX > 32 */
}
可见在线程调度器初始化时,就对我们上面所说的每个优先级的队列数组进行了初始化。
调度器启动
接下来我们对调度器启动函数 rt_system_scheduler_start 进行分析( 去掉多余定义后 ):
void rt_system_scheduler_start(void)
{
register struct rt_thread *to_thread;
rt_ubase_t highest_ready_priority;
/* 查找出新的最高优先级的线程 */
to_thread = _get_highest_priority_thread(&highest_ready_priority);
/* 切换到该线程 */
rt_current_thread = to_thread;
/* 将该线程从就绪列表移除并切换为运行状态 */
rt_schedule_remove_thread(to_thread);
to_thread->stat = RT_THREAD_RUNNING;
/* 切换到目的线程(此函数是与MCU相关的函数) */
rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp);
/* never come back */
}
根据上面的源码可分析出,在启用调度器时,调度器会从就绪链表中找出当前优先级最高的线程并切换该线程状态为运行状态。
线程调度核心代码简析
上述分析是针对整个系统初始化线程调度器及启用调度器时所作的处理,接下来我们分析系统运行时的线程调度。
rt_schedule
该函数将执行一次调度。它将选择一个最高优先级的就绪线程线程并立即切换到它。
源码如下( 去掉多余定义后 ):
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)
{
rt_ubase_t highest_ready_priority;
/*
* 判断就绪链表是否存在线程
*(如果有新的线程创建,系统则会把对应的线程优先级在
* rt_thread_ready_priority_group 对应的 bit 位置 1 。)
*/
if (rt_thread_ready_priority_group != 0)
{
/* 将from_thread 插入就绪队列 */
int need_insert_from_thread = 0;
/* 获取就绪链表中最高优先级线程 */
to_thread = _get_highest_priority_thread(&highest_ready_priority);
/* 判断当前线程是否处于运行状态 */
if ((rt_current_thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING)
{
/* 判断当前线程的优先级 */
/* 如果当前线程优先级高于当前就绪优先级 */
if (rt_current_thread->current_priority < highest_ready_priority)
{
to_thread = rt_current_thread;
}
/* 如果当前线程优先级等于当前就绪线程优先级且未让出资源 */
else if (rt_current_thread->current_priority == highest_ready_priority && (rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) == 0)
{
to_thread = rt_current_thread;
}
/* 其它情况则需要将 from_thread 插入就绪链表 */
else
{
need_insert_from_thread = 1;
}
/* 当前线程让出 cpu 资源 */
rt_current_thread->stat &= ~RT_THREAD_STAT_YIELD_MASK;
}
/* 如果目标线程与当前线程不同则需要切换线程 */
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));
if (need_insert_from_thread)
{
rt_schedule_insert_thread(from_thread);
}
/* 将 to_thread 从就绪链表删除 */
rt_schedule_remove_thread(to_thread);
/* 将 to_thread 状态切换为运行态 */
to_thread->stat = RT_THREAD_RUNNING | (to_thread->stat & ~RT_THREAD_STAT_MASK);
/* 切换到新线程 */
/* 输出日志信息 */
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
("[%d]switch to priority#%d "
"thread:%.*s(sp:0x%08x), "
"from thread:%.*s(sp: 0x%08x)\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 /* RT_USING_OVERFLOW_CHECK */
/* 判断是否处于中断嵌套中 */
if (rt_interrupt_nest == 0)
{
/* 如果当前没有处于中断嵌套中 */
extern void rt_thread_handle_sig(rt_bool_t clean_state);
RT_OBJECT_HOOK_CALL(rt_scheduler_switch_hook, (from_thread));
/* 线程切换,此函数为 MCU 相关函数,与具体使用的 MCU 相关 */
rt_hw_context_switch((rt_ubase_t)&from_thread->sp,
(rt_ubase_t)&to_thread->sp);
/* 开启中断 */
rt_hw_interrupt_enable(level);
goto __exit;
}
else
{
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));
/* 线程切换,此函数为 MCU 相关函数,与具体使用的 MCU 相关 */
rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp,
(rt_ubase_t)&to_thread->sp);
}
}
else
{
/* 将 rt_current_thread 从就绪队列删除 */
rt_schedule_remove_thread(rt_current_thread);
/* 将 rt_current_thread 切换为运行状态 */
rt_current_thread->stat = RT_THREAD_RUNNING | (rt_current_thread->stat & ~RT_THREAD_STAT_MASK);
}
}
}
/* 开启中断 */
rt_hw_interrupt_enable(level);
__exit:
return;
}
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();
/* 判断是否为当前线程(当前线程为运行态) */
if (thread == rt_current_thread)
{
thread->stat = RT_THREAD_RUNNING | (thread->stat & ~RT_THREAD_STAT_MASK);
goto __exit;
}
/* 状态改为就绪态并插入就绪链表 */
thread->stat = RT_THREAD_READY | (thread->stat & ~RT_THREAD_STAT_MASK);
/* 往当前优先级链表中添加线程节点 */
rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),
&(thread->tlist));
/* 打印日志信息 */
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("insert thread[%.*s], the priority: %d\n",
RT_NAME_MAX, thread->name, thread->current_priority));
/* set priority mask */
#if RT_THREAD_PRIORITY_MAX > 32
rt_thread_ready_table[thread->number] |= thread->high_mask;
#endif /* RT_THREAD_PRIORITY_MAX > 32 */
rt_thread_ready_priority_group |= thread->number_mask;
__exit:
/* 开启中断 */
rt_hw_interrupt_enable(temp);
}
rt_schedule_remove_thread
此函数将从系统就绪队列中删除线程。
void rt_schedule_remove_thread(struct rt_thread *thread)
{
register rt_base_t level;
/* 断言检查线程指针是否为空 */
RT_ASSERT(thread != RT_NULL);
/* 关中断 */
level = rt_hw_interrupt_disable();
/* 打印日志输出 */
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("remove thread[%.*s], the priority: %d\n",
RT_NAME_MAX, thread->name,
thread->current_priority));
/* 从就绪链表移除 */
rt_list_remove(&(thread->tlist));
/* 如果当前优先级链表已空 */
if (rt_list_isempty(&(rt_thread_priority_table[thread->current_priority])))
{
#if RT_THREAD_PRIORITY_MAX > 32
rt_thread_ready_table[thread->number] &= ~thread->high_mask;
if (rt_thread_ready_table[thread->number] == 0)
{
rt_thread_ready_priority_group &= ~thread->number_mask;
}
#else
rt_thread_ready_priority_group &= ~thread->number_mask;
#endif /* RT_THREAD_PRIORITY_MAX > 32 */
}
/* 开中断 */
rt_hw_interrupt_enable(level);
}
rt_enter_critical
此函数会将调度器上锁,也就是禁止调度,执行了此函数,调度器将不再调度线程,如上述 rt_schedule函数中会有判断 if (rt_scheduler_lock_nest == 0) 。
void rt_enter_critical(void)
{
register rt_base_t level;
/* 关中断 */
level = rt_hw_interrupt_disable();
/*
* the maximal number of nest is RT_UINT16_MAX, which is big
* enough and does not check here
*/
rt_scheduler_lock_nest ++;//调度锁计数加一
/* 开中断 */
rt_hw_interrupt_enable(level);
}
rt_exit_critical
与进入临界区对应,此函数实现的是让之前禁止的调度器重新调度线程。
void rt_exit_critical(void)
{
register rt_base_t level;
/* 关中断 */
level = rt_hw_interrupt_disable();
rt_scheduler_lock_nest --;//调度锁计数器减一
/* 如果调度锁计数器小于等于 0 ,则将其置 0 */
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_enter_critical(); //进入临界区,退出前系统不会发生任务调度
/*
*
* 临界区代码
*
*/
rt_exit_critical(); //退出临界区
临界区的代码尽量短,不要过多地占用CPU的时间。因为这段时间,系统是不能产生任务调度的,如果临界区代码消耗CPU的时间过多,就不能保证别的线程能够得到实时的响应,也就失去了实时操作系统的优势。