承接上文分析完线程,此文研究线程调度,由初始化函数rt_system_scheduler_init开始。
scheduler.c文件
void rt_system_scheduler_init(void)
{
#ifdef RT_USING_SMP//多核宏,暂不管
int cpu;
#endif /*RT_USING_SMP*/
register rt_base_t offset;
#ifndef RT_USING_SMP
rt_scheduler_lock_nest = 0;
#endif /*RT_USING_SMP*/
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("start scheduler: max priority 0x%02x\n",
RT_THREAD_PRIORITY_MAX));//优先级在rtconfig.h中被定义
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
{
//rt_thread_priority_table被用来实现优先级的管理,是一个双向链表,这里初始化是将链表指针置空
rt_list_init(&rt_thread_priority_table[offset]);
}
#ifdef RT_USING_SMP
for (cpu = 0; cpu < RT_CPUS_NR; cpu++)
{
struct rt_cpu *pcpu = rt_cpu_index(cpu);
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
{
rt_list_init(&pcpu->priority_table[offset]);
}
pcpu->irq_switch_flag = 0;
pcpu->current_priority = RT_THREAD_PRIORITY_MAX - 1;
pcpu->current_thread = RT_NULL;
pcpu->priority_group = 0;
#if RT_THREAD_PRIORITY_MAX > 32
rt_memset(pcpu->ready_table, 0, sizeof(pcpu->ready_table));
#endif
}
#endif /*RT_USING_SMP*/
/* initialize ready priority group */
rt_thread_ready_priority_group = 0;
#if RT_THREAD_PRIORITY_MAX > 32
/* initialize ready table */
rt_memset(rt_thread_ready_table, 0, sizeof(rt_thread_ready_table));
#endif
/* initialize thread defunct */
rt_list_init(&rt_thread_defunct);//初始化空闲回调的链表
}
该函数仅仅是在将之后需要的变量赋初值,实际启动调度管理的函数被执行在components.c中rtthread_startup函数的最后一步。如其名启动调度器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);
#ifdef RT_USING_SMP
to_thread->oncpu = rt_hw_cpu_id();
#else
rt_current_thread = to_thread;//将当前运行线程指针指向这个即将运行的线程
#endif /*RT_USING_SMP*/
rt_schedule_remove_thread(to_thread);//将该线程从线程调度器中移除
to_thread->stat = RT_THREAD_RUNNING;//切换为正在运行模式
/* switch to new thread */
#ifdef RT_USING_SMP
rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp, to_thread);
#else
rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp);//通过堆栈寄存器指针调整运行该进程
#endif /*RT_USING_SMP*/
/* never come back */
}
该函数很直白,就是找到就绪队列中最高优先级的线程,然后跳转运行。这里是用来启动第一个线程,仅会被执行一次。其中很多变量是在线程中被定义的,可以看前一篇文章了解。下文将先阅读线程启动函数中用到一些功能性函数。
首先是获取最高优先级线程指针的函数
static struct rt_thread* _get_highest_priority_thread(rt_ubase_t *highest_prio)
{
register struct rt_thread *highest_priority_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
/* get highest ready priority thread */
//通过得到的最高优先级 找到最近的一个线程
highest_priority_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
*highest_prio = highest_ready_priority;//保存该优先级
return highest_priority_thread;
}
这里补充一下优先级数组的存储方式,rt_thread_priority_table的每一个成员代表一种优先级,而每个成员是一张链表,则保存在该优先级上的所有线程。
![](https://img.haomeiwen.com/i2771446/06937c5bb3b8d350.png)
这里就是通过找到最高优先级,然后在找到该优先级下最近指向的线程来找到应该被执行的线程。
接下来再看看关于线程加入调度器的代码。
void rt_schedule_insert_thread(struct rt_thread *thread)
{
register rt_base_t temp;
RT_ASSERT(thread != RT_NULL);
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* it's current thread, it should be RUNNING thread */
//这里判断该进程是否是正在运行,如果是,则表明进程状态出错,将其修改后退出
if (thread == rt_current_thread)
{
thread->stat = RT_THREAD_RUNNING | (thread->stat & ~RT_THREAD_STAT_MASK);
goto __exit;
}
/* READY thread, insert to ready queue 将线程状态切换为就绪*/
thread->stat = RT_THREAD_READY | (thread->stat & ~RT_THREAD_STAT_MASK);
/* insert thread to ready list 通过线程的优先级插入到对应的链表中*/
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_ready_priority_group |= thread->number_mask;
__exit:
/* enable interrupt */
rt_hw_interrupt_enable(temp);
}
然后是将线程移除调度器函数。
void rt_schedule_remove_thread(struct rt_thread *thread)
{
register rt_base_t level;
RT_ASSERT(thread != RT_NULL);
/* disable interrupt */
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));
/* remove thread from ready list */
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
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
}
线程切换实际是通过rt_schedule函数来执行的,在任何地方通过执行它系统便从调度器中找出最高优先级的进程,通过比对当前执行中的线程优先级判断是否切换。
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) //确保没用被上锁
{
rt_ubase_t highest_ready_priority;
if (rt_thread_ready_priority_group != 0)//确保在调度器中有线程
{
/* need_insert_from_thread: need to insert from_thread to ready queue */
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
{
need_insert_from_thread = 1;
}
}
if (to_thread != rt_current_thread) //所得线程并不是正在运行的线程
{
/* if the destination thread is not the same as 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);
}
//将即将运行的线程从调度器中移除
rt_schedule_remove_thread(to_thread);
to_thread->stat = RT_THREAD_RUNNING | (to_thread->stat & ~RT_THREAD_STAT_MASK);//切换为运行态
/* switch to new thread */
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
if (rt_interrupt_nest == 0)//如果没有中断嵌套
{
extern void rt_thread_handle_sig(rt_bool_t clean_state);
//线程切换
rt_hw_context_switch((rt_ubase_t)&from_thread->sp,
(rt_ubase_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);
/* check signal status */
rt_thread_handle_sig(RT_TRUE);
}
else
{
rt_hw_interrupt_enable(level);
}
#else
/* enable interrupt */
rt_hw_interrupt_enable(level);
#endif
goto __exit;
}
else
{
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));
//线程切换
rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp,
(rt_ubase_t)&to_thread->sp);
}
}
else//所得线程即是正在运行线程
{
rt_schedule_remove_thread(rt_current_thread);
rt_current_thread->stat = RT_THREAD_RUNNING | (rt_current_thread->stat & ~RT_THREAD_STAT_MASK);
}
}
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
__exit:
return;
}
至于系统是如何自动执行线程切换的,就需要回到之前提到的线程部分,每个线程都有一个定时器,用于根据设定的时间片配置闹钟,当超时后会调用统一的超时函数rt_thread_timeout,在该函数中变调用了rt_schedule来实现线程切换。最终呈现出来的效果就是:
rt_system_scheduler_start启动第一个线程
第一个线程时间片耗尽,进入超时函数,切换线程,循环往复。在不加入新线程的情况下,就这样有序的从高到低优先级依次执行线程。至于高优先级打断低优先级的情况必然是当即执行了线程切换函数的,例如在新添加进程的函数中便执行了rt_schedule,通过全局查询rt_schedule便知道很多地方也调用到。
网友评论