在之前 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
大致的情况是:
1、低优先级的存在任务A(ticks = a),B(ticks =b),; 高优先级任务C
2、如果高优先级 C内存在延时c 正好等于A的时间片a
3、结果就是低优先级的任务只有A在一直运行, B一直运行不了
这种情况的根本原因其实还是笔者之前提到的高优先级导致当前低优先级任务插入readylist位置不对的issue,
下面笔者再次配重新整理一下这个问题,配合图例逐步分析源码并结合测试例程展示不同情况下该issue导致的问题,并尝试解决。
/**
* @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();
}
里面只做了两件事:
1、当前任务的时间片递减, 如果用完了,置位RT_THREAD_STAT_YIELD状态,调用rt_schedule,2、检测是否有任务的超时了(等待资源或延时超时),如果超时,最终也会调用rt_schedule排除componets中使用的情况,rt_schedule主要在下面情况中被使用1、clock.c : 就是刚刚提及的在Systick中断中两种比较重要的调度: 时间片调度和超时调度2、ipc.c ,mempool.c: 另外一种比较重要的调度:资源阻塞和就绪调度(资源调度)3、scheduler.c, thread.c:本身调度器和线程API的使用导致的直接API调度4、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;
}
elseif(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), "
"from thread:%.*s(sp: 0x%08x) ",
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)
{
externvoid rt_thread_handle_sig(rt_bool_t clean_state);
RT_OBJECT_HOOK_CALL(rt_scheduler_switch_hook,(from_thread));
rt_hw_context_switch((rt_ubase_t)&from_thread->sp,
(rt_ubase_t)&to_thread->sp);
/* enable interrupt */
rt_hw_interrupt_enable(level);
#ifdef RT_USING_SIGNALS
/* check stat of thread for signal */
level = rt_hw_interrupt_disable();
if(rt_current_thread->stat & RT_THREAD_STAT_SIGNAL_PENDING)
{
externvoid 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);
}
#endif/* RT_USING_SIGNALS */
goto __exit;
}
else
{
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,("switch in interrupt "));
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;
}
之所以搞的这么复杂,是因为当前的调度策略是把运行的任务,移出了readylist,那么获取的highest_ready_priority,to_thread只是红色圈中的,还需要结合正在运行的 thread 再次判断,下面将结合下图说明上图中的1,2,3 中情况
假设 当前rt_thread_priority_table中存在三个优先级列表,下标分别为h, m, l (h
if(rt_current_thread->current_priority < highest_ready_priority)
1.1 低优先级就绪
存在 B,D,E3个任务, 当前 E 因资源阻塞或者延时中,B正在运行;某一时刻任务E就绪(资源就绪或者超时),插入对应readylist后发起一次调度:highest_ready_priority = l > m
对于某一任务的阻塞,图例仅展示资源阻塞(调度),或者延时阻塞(调度)中的一种,下同,不再说明
1.2 当前运行任务的时间片用完
同样存在 B,D,E 3个任务,当前B在运行; 某一时刻B的时间片用完,thread->stat |= RT_THREAD_STAT_YIELD,发起一次调度:highest_ready_priority = l > m因为最高优先级m下,只有B一个任务,所以尽管其时间片已经使用完,还会复位ticks,再次运行。
这两种情况,最后运行的还是当前任务
if(rt_current_thread->current_priority == highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)==0)
最后运行的也当前任务
可以直接列出剩余的三种情况,
3.1 当前优先级等于highest_ready_priority,且当前任务时间片用完
if(rt_current_thread->current_priority == highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)!=0)
需要礼让,把当前任务B插入对应优先级readylist后, 然后切换到C
3.2 当前优先级大于highest_ready_priority,且当前任务时间片用完
if(rt_current_thread->current_priority > highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)!=0)
if(rt_current_thread->current_priority > highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)==0)
高优先级就绪后会把当前任务插入的readylist最后,也就是这个插入导致了众多的问题出现。
void rt_schedule_insert_thread(struct rt_thread *thread)
{
registerrt_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 ",
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:
/* enable interrupt */
rt_hw_interrupt_enable(temp);
}
int main(void)
{
rt_thread_init(&rt_led1_thread,
"LED1",
led1_thread_entry,
RT_NULL,
&rt_led1_thread_stack[0],
sizeof(rt_led1_thread_stack),
6,
1);
rt_thread_startup(&rt_led1_thread);
rt_thread_init(&rt_led2_thread,
"LED2",
led2_thread_entry,
RT_NULL,
&rt_led2_thread_stack[0],
sizeof(rt_led2_thread_stack),
11,
4);
rt_thread_startup(&rt_led2_thread);
rt_thread_init(&rt_led3_thread,
"LED3",
led3_thread_entry,
RT_NULL,
&rt_led3_thread_stack[0],
sizeof(rt_led3_thread_stack),
11,
2);
rt_thread_startup(&rt_led3_thread);
while(1)
{
rt_thread_mdelay(1000);
}
return RT_EOK;
}
/* customer short delay */
void delay (uint32_t count)
{
for(; count!=0; count--);
}
void led1_thread_entry(void*p_arg )
{
for(;;)
{
HAL_GPIO_WritePin(GPIOD, LD4_Pin, GPIO_PIN_SET);
rt_thread_delay(5);
HAL_GPIO_WritePin(GPIOD, LD4_Pin, GPIO_PIN_RESET);
rt_thread_delay(5);
}
}
void led2_thread_entry(void*p_arg )
{
for(;;)
{
HAL_GPIO_WritePin(GPIOD, LD5_Pin, GPIO_PIN_SET);
delay(300);
HAL_GPIO_WritePin(GPIOD, LD5_Pin, GPIO_PIN_RESET);
delay(300);
}
}
void led3_thread_entry(void*p_arg )
{
for(;;)
{
HAL_GPIO_WritePin(GPIOD, LD3_Pin, GPIO_PIN_SET);
delay(300);
HAL_GPIO_WritePin(GPIOD, LD3_Pin, GPIO_PIN_RESET);
delay(300);
}
}
1)t1 = 5, t2 = 4, t3=2
新就绪的任务优先级高于当前任务,当前任务时间片有剩余
通过逻辑分析仪抓取三个LED pin 电平
波形中可以明显看出thread2时间片 4ms(4 ticks) ,执行3ms后因为高优先级thread1打断,剩余的1ms会在thread3执行完后才执行。thread3甚至出现了连续执行的情况,稍后分析。总之时间片调度完全乱掉了。2)t1 = 5, t2 = 3, t3=2新就绪的任务优先级高于当前任务,当前任务时间片无剩余
还记得我们上面提到了3.2 不能同时满足的条件吗?
if(rt_current_thread->current_priority > highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)!=0)
这种情况造成的结果比较明显,导致thread3一直没有运行的机会了,也就是小伙伴发现的issue https://github.com/RT-Thread/rt-thread/issues/6092从现象上看情况3是最极端,最严重的,但是你可以立即发现。不像情况1,2虽然表面上每个thread还在执行,内部的时间片已经完全乱掉,往往这种隐形的issue会导致更加严重的后果。
这种做法应该没啥问题,暂未测试
直击问题本质,既然是插入的问题导致的,调整一下插入顺序即可
除了时间片使用完,需要yield,插入其readylist的末尾,其他情况均插入readylist的头部
再次测试t1 = 5, t2 = 5, t3=2,结果如下:
可以看到,ticks连续执行了,线程没有重复执行,没有跳过,也没有不被执行的,完美解决。
更近一步,可不可以不把运行的thread移除readylist呢?如果可以,没有remove,也就没有了insert,没有了issue
其实FreeRTOS使用的就是这种方案:
taskSELECT_HIGHEST_PRIORITY_TASK()
#define taskSELECT_HIGHEST_PRIORITY_TASK()
{
UBaseType_t uxTopPriority;
/* Find the highest priority list that contains ready tasks. */
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );
configASSERT( listCURRENT_LIST_LENGTH(&( pxReadyTasksLists[ uxTopPriority ]))>0);
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB,&( pxReadyTasksLists[ uxTopPriority ]));
}/* taskSELECT_HIGHEST_PRIORITY_TASK() */
listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )
{
List_t*const pxConstList =( pxList );
/* Increment the index to the next item and return the item, ensuring */
/* we don't return the marker used at the end of the list. */
( pxConstList )->pxIndex =( pxConstList )->pxIndex->pxNext;
if((void*)( pxConstList )->pxIndex ==(void*)&(( pxConstList )->xListEnd ))
{
( pxConstList )->pxIndex =( pxConstList )->pxIndex->pxNext;
}
( pxTCB )=( pxConstList )->pxIndex->pvOwner;
}
https://github.com/RT-Thread/rt-thread/pull/5954
就是基于该方案。
/**
* @brief move the list to its next's next position
*
* @param l list to insert it
*/
rt_inline void rt_list_jump_next(rt_list_t*l)
{
l->next->prev = l->prev;
l->prev->next= l->next;
l->prev = l->next;
l->next->next->prev = l;
l->next= l->next->next;
l->prev->next= l;
}
大致操作如下:
这种直接移动list head的做法,一次调度会有6次指针赋值操作,
而原来的一次调度remove(4次), insert(4次)加起来是8次指针赋值操作
--- a/src/scheduler.c
+++ b/src/scheduler.c
@@-180,6+180,19@@staticstruct rt_thread* _scheduler_get_highest_priority_thread(rt_ubase_t*high
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group)-1;
#endif/* RT_THREAD_PRIORITY_MAX > 32 */
+/* if current thread is yield , move the head of priority list to next and change its status to READY */
+if((rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)!=0)
+{
+if(rt_current_thread->tlist.next!= rt_current_thread->tlist.prev)
+{
+/* multiple threads, move the list head to next */
+ rt_list_jump_next(&rt_thread_priority_table[rt_current_thread->current_priority]);
+}
+
+/* clear YIELD and ready thread*/
+ rt_current_thread->stat = RT_THREAD_READY |(rt_current_thread->stat &~(RT_THREAD_STAT_YIELD_MASK|RT_THREAD_STAT_MASK));
+}
+
/* get highest ready priority thread */
highest_priority_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
@@-258,7+271,6@@void rt_system_scheduler_start(void)
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 */
@@-436,28+448,8@@void rt_schedule(void)
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;
-}
-elseif(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 */
@@-467,12+459,6@@void rt_schedule(void)
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 */
@@-531,7+517,6@@void rt_schedule(void)
}
else
{
- rt_schedule_remove_thread(rt_current_thread);
rt_current_thread->stat = RT_THREAD_RUNNING |(rt_current_thread->stat &~RT_THREAD_STAT_MASK);
}
}
(END)
@@-258,7+271,6@@void rt_system_scheduler_start(void)
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 */
同样测试t1 = 5, t2 = 5, t3=2,结果正常如下:
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !