本文由RT-Thread论坛用户@blta原创发布:https://club.rt-thread.org/ask/article/b3b36a52556382b2.html
在之前 rt_schedule中need_insert_from_thread的问题 提问中,笔者提出了当前时间片调度算法过于复杂,且高优先级一旦打断未执行完时间片的任务会导致该任务重新插入到其优先级readylist末尾,存在严重的不公平性(破坏了时间片的连续)。
当然笔者也PR了一个解决方案 https://github.com/RT-Thread/rt-thread/pull/5954 暂未合并
最近又有一个小伙伴发现了时间片调度的issue https://github.com/RT-Thread/rt-thread/issues/6092
大致的情况是:
- 低优先级的存在任务A(ticks = a),B(ticks =b),; 高优先级任务C
- 如果高优先级 C内存在延时c 正好等于A的时间片a
- 结果就是低优先级的任务只有A在一直运行, B一直运行不了
这种情况的根本原因其实还是笔者之前提到的高优先级导致当前低优先级任务插入readylist位置不对的issue,
下面笔者再次配重新整理一下这个问题,配合图例逐步分析源码并结合测试例程展示不同情况下该issue导致的问题,并尝试解决。
源码分析
rt_tick_increase
/**
* @brief This function will notify kernel there is one tick passed.
* Normally, this function is invoked by clock ISR.
*/
void rt_tick_increase(void)
{
struct rt_thread *thread;
rt_base_t level;
RT_OBJECT_HOOK_CALL(rt_tick_hook, ());
level = rt_hw_interrupt_disable();
/* increase the global tick */
#ifdef RT_USING_SMP
rt_cpu_self()->tick ++;
#else
++ rt_tick;
#endif /* RT_USING_SMP */
/* check time slice */
thread = rt_thread_self();
-- thread->remaining_tick;
if (thread->remaining_tick == 0)
{
/* change to initialized tick */
thread->remaining_tick = thread->init_tick;
thread->stat |= RT_THREAD_STAT_YIELD;
rt_hw_interrupt_enable(level);
rt_schedule();
}
else
{
rt_hw_interrupt_enable(level);
}
/* check timer */
rt_timer_check();
}
里面只做了两件事:
- 当前任务的时间片递减, 如果用完了,置位RT_THREAD_STAT_YIELD状态,调用rt_schedule,
- 检测是否有任务的超时了(等待资源或延时超时),如果超时,最终也会调用rt_schedule
rt_schedule
Who calling
排除componets中使用的情况,rt_schedule主要在下面情况中被使用
- clock.c : 就是刚刚提及的在Systick中断中两种比较重要的调度: 时间片调度和超时调度
- ipc.c ,mempool.c: 另外一种比较重要的调度: 资源阻塞和就绪调度(资源调度)
- scheduler.c, thread.c: 本身调度器和线程API的使用导致的直接API调度
- timer.c : 软定时器超时调度,使用的也是_thread_timeout超时函数,也是超时调度
鉴于 API调度一般使用在初始化阶段,Application运行中主要使用的是时间片调度,超时调度,资源调度 。后面的讨论中主要绕后三种展开:
源码
/**
* @brief This function will perform scheduling once. It will select one thread
* with the highest priority, and switch to it immediately.
*/
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 = _scheduler_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;
}
else
{
need_insert_from_thread = 1;
}
rt_current_thread->stat &= ~RT_THREAD_STAT_YIELD_MASK;
}
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), "