0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

時(shí)間片調(diào)度算法issue詳解

RTThread物聯(lián)網(wǎng)操作系統(tǒng) ? 來(lái)源:RTThread物聯(lián)網(wǎng)操作系統(tǒng) ? 作者:blta ? 2022-07-10 13:23 ? 次閱讀

在之前 rt_schedule中need_insert_from_thread的問(wèn)題提問(wèn)中,筆者提出了當(dāng)前時(shí)間片調(diào)度算法過(guò)于復(fù)雜,且高優(yōu)先級(jí)一旦打斷未執(zhí)行完時(shí)間片的任務(wù)會(huì)導(dǎo)致該任務(wù)重新插入到其優(yōu)先級(jí)readylist末尾,存在嚴(yán)重的不公平性(破壞了時(shí)間片的連續(xù))。

當(dāng)然筆者也PR了一個(gè)解決方案(暫未合并)

https://github.com/RT-Thread/rt-thread/pull/5954

最近又有一個(gè)小伙伴發(fā)現(xiàn)了時(shí)間片調(diào)度的issue

https://github.com/RT-Thread/rt-thread/issues/6092

大致的情況是:

1、低優(yōu)先級(jí)的存在任務(wù)A(ticks = a),B(ticks =b),; 高優(yōu)先級(jí)任務(wù)C

2、如果高優(yōu)先級(jí) C內(nèi)存在延時(shí)c 正好等于A的時(shí)間片a

3、結(jié)果就是低優(yōu)先級(jí)的任務(wù)只有A在一直運(yùn)行, B一直運(yùn)行不了

這種情況的根本原因其實(shí)還是筆者之前提到的高優(yōu)先級(jí)導(dǎo)致當(dāng)前低優(yōu)先級(jí)任務(wù)插入readylist位置不對(duì)的issue,

下面筆者再次配重新整理一下這個(gè)問(wèn)題,配合圖例逐步分析源碼并結(jié)合測(cè)試?yán)陶故静煌闆r下該issue導(dǎo)致的問(wèn)題,并嘗試解決。

源碼分析

rt_tick_increase


	
  1. /**

  2. * @brief This function will notify kernel there is one tick passed.

  3. * Normally, this function is invoked by clock ISR.

  4. */

  5. void rt_tick_increase(void)

  6. {

  7. struct rt_thread *thread;

  8. rt_base_t level;

  9. RT_OBJECT_HOOK_CALL(rt_tick_hook,());

  10. level = rt_hw_interrupt_disable();

  11. /* increase the global tick */

  12. #ifdef RT_USING_SMP

  13. rt_cpu_self()->tick ++;

  14. #else

  15. ++ rt_tick;

  16. #endif/* RT_USING_SMP */

  17. /* check time slice */

  18. thread = rt_thread_self();

  19. -- thread->remaining_tick;

  20. if(thread->remaining_tick ==0)

  21. {

  22. /* change to initialized tick */

  23. thread->remaining_tick = thread->init_tick;

  24. thread->stat |= RT_THREAD_STAT_YIELD;

  25. rt_hw_interrupt_enable(level);

  26. rt_schedule();

  27. }

  28. else

  29. {

  30. rt_hw_interrupt_enable(level);

  31. }

  32. /* check timer */

  33. rt_timer_check();

  34. }

里面只做了兩件事:

1、當(dāng)前任務(wù)的時(shí)間片遞減, 如果用完了,置位RT_THREAD_STAT_YIELD狀態(tài),調(diào)用rt_schedule,2、檢測(cè)是否有任務(wù)的超時(shí)了(等待資源或延時(shí)超時(shí)),如果超時(shí),最終也會(huì)調(diào)用rt_schedule

rt_schedule

Who calling

f4ea8490-f6d0-11ec-ba43-dac502259ad0.jpg

排除componets中使用的情況,rt_schedule主要在下面情況中被使用1、clock.c : 就是剛剛提及的在Systick中斷中兩種比較重要的調(diào)度:時(shí)間片調(diào)度超時(shí)調(diào)度2、ipc.c ,mempool.c: 另外一種比較重要的調(diào)度:資源阻塞和就緒調(diào)度(資源調(diào)度3、scheduler.c, thread.c:本身調(diào)度器和線程API的使用導(dǎo)致的直接API調(diào)度4、timer.c : 軟定時(shí)器超時(shí)調(diào)度,使用的也是_thread_timeout超時(shí)函數(shù),也是超時(shí)調(diào)度 鑒于API調(diào)度一般使用在初始化階段,Application運(yùn)行中主要使用的是時(shí)間片調(diào)度,超時(shí)調(diào)度,資源調(diào)度。后面的討論中主要繞后三種展開(kāi): f4fc739e-f6d0-11ec-ba43-dac502259ad0.jpg

源碼


	
  1. /**

  2. * @brief This function will perform scheduling once. It will select one thread

  3. * with the highest priority, and switch to it immediately.

  4. */

  5. void rt_schedule(void)

  6. {

  7. rt_base_t level;

  8. struct rt_thread *to_thread;

  9. struct rt_thread *from_thread;

  10. /* disable interrupt */

  11. level = rt_hw_interrupt_disable();

  12. /* check the scheduler is enabled or not */

  13. if(rt_scheduler_lock_nest ==0)

  14. {

  15. rt_ubase_t highest_ready_priority;

  16. if(rt_thread_ready_priority_group !=0)

  17. {

  18. /* need_insert_from_thread: need to insert from_thread to ready queue */

  19. int need_insert_from_thread =0;

  20. to_thread = _scheduler_get_highest_priority_thread(&highest_ready_priority);

  21. if((rt_current_thread->stat & RT_THREAD_STAT_MASK)== RT_THREAD_RUNNING)

  22. {

  23. if(rt_current_thread->current_priority < highest_ready_priority)

  24. {

  25. to_thread = rt_current_thread;

  26. }

  27. elseif(rt_current_thread->current_priority == highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)==0)

  28. {

  29. to_thread = rt_current_thread;

  30. }

  31. else

  32. {

  33. need_insert_from_thread =1;

  34. }

  35. rt_current_thread->stat &=~RT_THREAD_STAT_YIELD_MASK;

  36. }

  37. if(to_thread != rt_current_thread)

  38. {

  39. /* if the destination thread is not the same as current thread */

  40. rt_current_priority =(rt_uint8_t)highest_ready_priority;

  41. from_thread = rt_current_thread;

  42. rt_current_thread = to_thread;

  43. RT_OBJECT_HOOK_CALL(rt_scheduler_hook,(from_thread, to_thread));

  44. if(need_insert_from_thread)

  45. {

  46. rt_schedule_insert_thread(from_thread);

  47. }

  48. rt_schedule_remove_thread(to_thread);

  49. to_thread->stat = RT_THREAD_RUNNING |(to_thread->stat &~RT_THREAD_STAT_MASK);

  50. /* switch to new thread */

  51. RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,

  52. ("[%d]switch to priority#%d "

  53. "thread:%.*s(sp:0x%08x), "

  54. "from thread:%.*s(sp: 0x%08x) ",

  55. rt_interrupt_nest, highest_ready_priority,

  56. RT_NAME_MAX, to_thread->name, to_thread->sp,

  57. RT_NAME_MAX, from_thread->name, from_thread->sp));

  58. #ifdef RT_USING_OVERFLOW_CHECK

  59. _rt_scheduler_stack_check(to_thread);

  60. #endif/* RT_USING_OVERFLOW_CHECK */

  61. if(rt_interrupt_nest ==0)

  62. {

  63. externvoid rt_thread_handle_sig(rt_bool_t clean_state);

  64. RT_OBJECT_HOOK_CALL(rt_scheduler_switch_hook,(from_thread));

  65. rt_hw_context_switch((rt_ubase_t)&from_thread->sp,

  66. (rt_ubase_t)&to_thread->sp);

  67. /* enable interrupt */

  68. rt_hw_interrupt_enable(level);

  69. #ifdef RT_USING_SIGNALS

  70. /* check stat of thread for signal */

  71. level = rt_hw_interrupt_disable();

  72. if(rt_current_thread->stat & RT_THREAD_STAT_SIGNAL_PENDING)

  73. {

  74. externvoid rt_thread_handle_sig(rt_bool_t clean_state);

  75. rt_current_thread->stat &=~RT_THREAD_STAT_SIGNAL_PENDING;

  76. rt_hw_interrupt_enable(level);

  77. /* check signal status */

  78. rt_thread_handle_sig(RT_TRUE);

  79. }

  80. else

  81. {

  82. rt_hw_interrupt_enable(level);

  83. }

  84. #endif/* RT_USING_SIGNALS */

  85. goto __exit;

  86. }

  87. else

  88. {

  89. RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,("switch in interrupt "));

  90. rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp,

  91. (rt_ubase_t)&to_thread->sp);

  92. }

  93. }

  94. else

  95. {

  96. rt_schedule_remove_thread(rt_current_thread);

  97. rt_current_thread->stat = RT_THREAD_RUNNING |(rt_current_thread->stat &~RT_THREAD_STAT_MASK);

  98. }

  99. }

  100. }

  101. /* enable interrupt */

  102. rt_hw_interrupt_enable(level);

  103. __exit:

  104. return;

  105. }

調(diào)度的過(guò)程大致如下:1、關(guān)中斷2、判斷調(diào)度是否上鎖,rt_thread_ready_priority_group是否為03、_scheduler_get_highest_priority_thread 獲取當(dāng)前最高優(yōu)先級(jí)4、和當(dāng)前優(yōu)先級(jí)再次比較,確定真實(shí)的最高優(yōu)先級(jí)5、獲取最高優(yōu)先級(jí)readylist的第一個(gè)任務(wù) to_thread6、to_thread 和 rt_current_thread 比較7、如果相等,簡(jiǎn)單設(shè)置狀態(tài),繼續(xù)運(yùn)行8、如果不相等,把當(dāng)前任務(wù)插入其優(yōu)先級(jí)readylist , 切換到 to_thread9、開(kāi)中斷

得到to_thread的再判斷

f50d01a0-f6d0-11ec-ba43-dac502259ad0.jpg

之所以搞的這么復(fù)雜,是因?yàn)楫?dāng)前的調(diào)度策略是把運(yùn)行的任務(wù),移出了readylist,那么獲取的highest_ready_priority,to_thread只是紅色圈中的,還需要結(jié)合正在運(yùn)行的 thread 再次判斷,下面將結(jié)合下圖說(shuō)明上圖中的1,2,3 中情況

f52013ee-f6d0-11ec-ba43-dac502259ad0.jpg

假設(shè) 當(dāng)前rt_thread_priority_table中存在三個(gè)優(yōu)先級(jí)列表,下標(biāo)分別為h, m, l (h

1. 當(dāng)前優(yōu)先級(jí)小于highest_ready_priority


	
  1. if(rt_current_thread->current_priority < highest_ready_priority)

這個(gè)很好理解:沒(méi)有和當(dāng)前任務(wù)優(yōu)先級(jí)相同的任務(wù),其優(yōu)先級(jí)readylist為空,存在如下兩種情況:

1.1 低優(yōu)先級(jí)就緒

存在 B,D,E3個(gè)任務(wù), 當(dāng)前 E 因資源阻塞或者延時(shí)中,B正在運(yùn)行;某一時(shí)刻任務(wù)E就緒(資源就緒或者超時(shí)),插入對(duì)應(yīng)readylist后發(fā)起一次調(diào)度:highest_ready_priority = l > m

f532babc-f6d0-11ec-ba43-dac502259ad0.jpg

對(duì)于某一任務(wù)的阻塞,圖例僅展示資源阻塞(調(diào)度),或者延時(shí)阻塞(調(diào)度)中的一種,下同,不再說(shuō)明

1.2 當(dāng)前運(yùn)行任務(wù)的時(shí)間片用完

同樣存在 B,D,E 3個(gè)任務(wù),當(dāng)前B在運(yùn)行; 某一時(shí)刻B的時(shí)間片用完,thread->stat |= RT_THREAD_STAT_YIELD,發(fā)起一次調(diào)度:highest_ready_priority = l > m因?yàn)樽罡邇?yōu)先級(jí)m下,只有B一個(gè)任務(wù),所以盡管其時(shí)間片已經(jīng)使用完,還會(huì)復(fù)位ticks,再次運(yùn)行。

f532babc-f6d0-11ec-ba43-dac502259ad0.jpg

這兩種情況,最后運(yùn)行的還是當(dāng)前任務(wù)

2. 當(dāng)前優(yōu)先級(jí)等于highest_ready_priority,且當(dāng)前任務(wù)時(shí)間片未用完


	
  1. if(rt_current_thread->current_priority == highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)==0)

存在 B,C 兩個(gè)同優(yōu)先級(jí)的任務(wù),當(dāng)前C因資源阻塞或者延時(shí)中,B正在運(yùn)行;某一時(shí)刻C就緒,插入對(duì)應(yīng)readylist后發(fā)起一次調(diào)度:highest_ready_priority = m但此時(shí)B的時(shí)間片還未用完,依舊無(wú)需禮讓切換,讓C等著吧。

f54f2364-f6d0-11ec-ba43-dac502259ad0.jpg

最后運(yùn)行的也當(dāng)前任務(wù)

3 需要切換新的任務(wù)

可以直接列出剩余的三種情況,

3.1 當(dāng)前優(yōu)先級(jí)等于highest_ready_priority,且當(dāng)前任務(wù)時(shí)間片用完


	
  1. if(rt_current_thread->current_priority == highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)!=0)

存在 B,C 兩個(gè)同優(yōu)先級(jí)的任務(wù),當(dāng)前C已經(jīng)resume就緒, B正在運(yùn)行;某一時(shí)刻B的時(shí)間片用完,thread->stat |= RT_THREAD_STAT_YIELD,發(fā)起一次調(diào)度:highest_ready_priority = m

f54f2364-f6d0-11ec-ba43-dac502259ad0.jpg

需要禮讓,把當(dāng)前任務(wù)B插入對(duì)應(yīng)優(yōu)先級(jí)readylist后, 然后切換到C

3.2 當(dāng)前優(yōu)先級(jí)大于highest_ready_priority,且當(dāng)前任務(wù)時(shí)間片用完


	
  1. if(rt_current_thread->current_priority > highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)!=0)

這個(gè)情況實(shí)際是不存在的:(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) != 0 表示時(shí)間片用完了,是通過(guò)rt_tick_increase->rt_schedule,即時(shí)間片用完,置位RT_THREAD_STAT_YIELD后發(fā)起的一次rt_schedule調(diào)度,調(diào)度結(jié)束會(huì)清除RT_THREAD_STAT_YIELD狀態(tài)rt_current_thread->current_priority > highest_ready_priority表示高優(yōu)先級(jí)就緒,是通過(guò) rt_tick_increase->rt_timer_check->_thread_timeout->rt_schedule即高優(yōu)先級(jí)的超時(shí)函數(shù)觸發(fā)調(diào)度一次rt_schedule調(diào)度很明顯,這兩種調(diào)度不會(huì)同時(shí)發(fā)生,就算rt_tick_increase->rt_schedule先發(fā)生,也會(huì)清除RT_THREAD_STAT_YIELD狀態(tài),導(dǎo)致條件不滿足。3.3 當(dāng)前優(yōu)先級(jí)大于highest_ready_priority,且當(dāng)前任務(wù)時(shí)間片未用完

	
  1. if(rt_current_thread->current_priority > highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)==0)

存在任務(wù) ABCDE, 當(dāng)前A,E因資源阻塞或者延時(shí)中,B正在運(yùn)行;某一時(shí)刻A就緒,其任務(wù)優(yōu)先級(jí)高于當(dāng)前任務(wù),雖然當(dāng)前任務(wù)時(shí)間片有剩余,也必須切出

f57eb1e2-f6d0-11ec-ba43-dac502259ad0.jpg

高優(yōu)先級(jí)就緒后會(huì)把當(dāng)前任務(wù)插入的readylist最后,也就是這個(gè)插入導(dǎo)致了眾多的問(wèn)題出現(xiàn)。

f58c3d80-f6d0-11ec-ba43-dac502259ad0.jpg

rt_schedule_insert_thread


	
  1. void rt_schedule_insert_thread(struct rt_thread *thread)

  2. {

  3. registerrt_base_t temp;

  4. RT_ASSERT(thread != RT_NULL);

  5. /* disable interrupt */

  6. temp = rt_hw_interrupt_disable();

  7. /* it's current thread, it should be RUNNING thread */

  8. if(thread == rt_current_thread)

  9. {

  10. thread->stat = RT_THREAD_RUNNING |(thread->stat &~RT_THREAD_STAT_MASK);

  11. goto __exit;

  12. }

  13. /* READY thread, insert to ready queue */

  14. thread->stat = RT_THREAD_READY |(thread->stat &~RT_THREAD_STAT_MASK);

  15. /* insert thread to ready list */

  16. rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),

  17. &(thread->tlist));

  18. RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,("insert thread[%.*s], the priority: %d ",

  19. RT_NAME_MAX, thread->name, thread->current_priority));

  20. /* set priority mask */

  21. #if RT_THREAD_PRIORITY_MAX > 32

  22. rt_thread_ready_table[thread->number]|= thread->high_mask;

  23. #endif/* RT_THREAD_PRIORITY_MAX > 32 */

  24. rt_thread_ready_priority_group |= thread->number_mask;

  25. __exit:

  26. /* enable interrupt */

  27. rt_hw_interrupt_enable(temp);

  28. }

可以明顯看出,rt_schedule_insert_thread調(diào)用的是rt_list_insert_before,即把當(dāng)然運(yùn)行任務(wù)插入到其優(yōu)先級(jí)readylist的最后

問(wèn)題

因?yàn)楫?dāng)前運(yùn)行線程是remove出readylist的, 再插入readylist是必需的,但是使用rt_list_insert_before把未執(zhí)行完時(shí)間片的任務(wù)插入到readylist的最后面,下次輪到該優(yōu)先級(jí)時(shí),會(huì)直接執(zhí)行C,B的時(shí)間片被分成了兩次或以上來(lái)執(zhí)行,同理C也可能面臨同樣的情況。這就導(dǎo)致了很多問(wèn)題。

f59b55ea-f6d0-11ec-ba43-dac502259ad0.jpg

測(cè)試

測(cè)試環(huán)境:基于stm32f4disc1開(kāi)發(fā)板,創(chuàng)建3個(gè)線程分別控制LED3,LED4,LED5閃爍1、rt_led1_thread:優(yōu)先級(jí)9,時(shí)間片1,延時(shí) t1 = 5ms閃爍LED42、rt_led2_thread:優(yōu)先級(jí)11,時(shí)間片t2 =4(ms),自定義200延時(shí)閃爍LED53、rt_led3_thread:優(yōu)先級(jí)11,時(shí)間片t3 = 2(ms),自定義300延時(shí)閃爍LED3

創(chuàng)建線程


	
  1. int main(void)

  2. {

  3. rt_thread_init(&rt_led1_thread,

  4. "LED1",

  5. led1_thread_entry,

  6. RT_NULL,

  7. &rt_led1_thread_stack[0],

  8. sizeof(rt_led1_thread_stack),

  9. 6,

  10. 1);

  11. rt_thread_startup(&rt_led1_thread);

  12. rt_thread_init(&rt_led2_thread,

  13. "LED2",

  14. led2_thread_entry,

  15. RT_NULL,

  16. &rt_led2_thread_stack[0],

  17. sizeof(rt_led2_thread_stack),

  18. 11,

  19. 4);

  20. rt_thread_startup(&rt_led2_thread);

  21. rt_thread_init(&rt_led3_thread,

  22. "LED3",

  23. led3_thread_entry,

  24. RT_NULL,

  25. &rt_led3_thread_stack[0],

  26. sizeof(rt_led3_thread_stack),

  27. 11,

  28. 2);

  29. rt_thread_startup(&rt_led3_thread);

  30. while(1)

  31. {

  32. rt_thread_mdelay(1000);

  33. }

  34. return RT_EOK;

  35. }

線程入口函數(shù)


	
  1. /* customer short delay */

  2. void delay (uint32_t count)

  3. {

  4. for(; count!=0; count--);

  5. }

  6. void led1_thread_entry(void*p_arg )

  7. {

  8. for(;;)

  9. {

  10. HAL_GPIO_WritePin(GPIOD, LD4_Pin, GPIO_PIN_SET);

  11. rt_thread_delay(5);

  12. HAL_GPIO_WritePin(GPIOD, LD4_Pin, GPIO_PIN_RESET);

  13. rt_thread_delay(5);

  14. }

  15. }

  16. void led2_thread_entry(void*p_arg )

  17. {

  18. for(;;)

  19. {

  20. HAL_GPIO_WritePin(GPIOD, LD5_Pin, GPIO_PIN_SET);

  21. delay(300);

  22. HAL_GPIO_WritePin(GPIOD, LD5_Pin, GPIO_PIN_RESET);

  23. delay(300);

  24. }

  25. }

  26. void led3_thread_entry(void*p_arg )

  27. {

  28. for(;;)

  29. {

  30. HAL_GPIO_WritePin(GPIOD, LD3_Pin, GPIO_PIN_SET);

  31. delay(300);

  32. HAL_GPIO_WritePin(GPIOD, LD3_Pin, GPIO_PIN_RESET);

  33. delay(300);

  34. }

  35. }

不同時(shí)間片對(duì)調(diào)度的影響

1)t1 = 5, t2 = 4, t3=2

新就緒的任務(wù)優(yōu)先級(jí)高于當(dāng)前任務(wù),當(dāng)前任務(wù)時(shí)間片有剩余

通過(guò)邏輯分析儀抓取三個(gè)LED pin 電平 f5acfe26-f6d0-11ec-ba43-dac502259ad0.jpg

波形中可以明顯看出thread2時(shí)間片 4ms(4 ticks) ,執(zhí)行3ms后因?yàn)楦邇?yōu)先級(jí)thread1打斷,剩余的1ms會(huì)在thread3執(zhí)行完后才執(zhí)行。thread3甚至出現(xiàn)了連續(xù)執(zhí)行的情況,稍后分析??傊畷r(shí)間片調(diào)度完全亂掉了。2)t1 = 5, t2 = 3, t3=2新就緒的任務(wù)優(yōu)先級(jí)高于當(dāng)前任務(wù),當(dāng)前任務(wù)時(shí)間片無(wú)剩余

f5c01b50-f6d0-11ec-ba43-dac502259ad0.jpg

還記得我們上面提到了3.2 不能同時(shí)滿足的條件嗎?


	
  1. if(rt_current_thread->current_priority > highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)!=0)

只是因?yàn)檐浖膶?xiě)法(第一次rt_scheduler后清除了RT_THREAD_STAT_YIELD狀態(tài)),這個(gè)條件不能同時(shí)滿足。從波形圖看這兩條件是可以同時(shí)滿足的:某一個(gè)tick中斷了,線程thread2的時(shí)間片用完了,時(shí)間片調(diào)度度后, rt_timer_check發(fā)現(xiàn)正好有一個(gè)高優(yōu)先級(jí)線程超時(shí)了,于是先后觸發(fā)了兩次調(diào)度:第一次:時(shí)間片調(diào)度, rt_current = thread3, rt_thread_priority_table[11] -> thread2第二次:超時(shí)調(diào)度,把rt_current插入其readylist , rt_thread_priority_table[11] -> thread2—>thread3, rt_current = thread2然后,當(dāng)高優(yōu)先級(jí)thread1再次阻塞時(shí),首先運(yùn)行的還是thread2。這就是為什么thread2連續(xù)運(yùn)行了兩個(gè)t2(3*2),同理 thread3也是, 和我們期望的輪流執(zhí)行不一致。3)t1 = 5, t2 = 5, t3=x (t2/t3 =5)高優(yōu)先級(jí)的延時(shí)正好等于某一低優(yōu)先級(jí)線程的時(shí)間片

f5d155f0-f6d0-11ec-ba43-dac502259ad0.jpg

這種情況造成的結(jié)果比較明顯,導(dǎo)致thread3一直沒(méi)有運(yùn)行的機(jī)會(huì)了,也就是小伙伴發(fā)現(xiàn)的issuehttps://github.com/RT-Thread/rt-thread/issues/6092從現(xiàn)象上看情況3是最極端,最嚴(yán)重的,但是你可以立即發(fā)現(xiàn)。不像情況1,2雖然表面上每個(gè)thread還在執(zhí)行,內(nèi)部的時(shí)間片已經(jīng)完全亂掉,往往這種隱形的issue會(huì)導(dǎo)致更加嚴(yán)重的后果。

解決辦法

既然看到了時(shí)間片調(diào)度的眾多issue,也知道了問(wèn)題的原因:高優(yōu)先級(jí)打斷未執(zhí)行完時(shí)間片的任務(wù)導(dǎo)致該任務(wù)被重新插入到其優(yōu)先級(jí)readylist末尾那么就開(kāi)始著手解決。

方案一

先看下小伙伴同時(shí)提出的解決辦法:https://github.com/RT-Thread/rt-thread/pull/6095/files他增加了一個(gè)RT_THREAD_STAT_SCHEDULING 狀態(tài)來(lái)判斷和避免issue

f5df48c2-f6d0-11ec-ba43-dac502259ad0.jpg

這種做法應(yīng)該沒(méi)啥問(wèn)題,暫未測(cè)試

方案二

直擊問(wèn)題本質(zhì),既然是插入的問(wèn)題導(dǎo)致的,調(diào)整一下插入順序即可

f5efef4c-f6d0-11ec-ba43-dac502259ad0.jpg

除了時(shí)間片使用完,需要yield,插入其readylist的末尾,其他情況均插入readylist的頭部

再次測(cè)試t1 = 5, t2 = 5, t3=2,結(jié)果如下:

f5ff5ca2-f6d0-11ec-ba43-dac502259ad0.jpg

可以看到,ticks連續(xù)執(zhí)行了,線程沒(méi)有重復(fù)執(zhí)行,沒(méi)有跳過(guò),也沒(méi)有不被執(zhí)行的,完美解決。

方案三

更近一步,可不可以不把運(yùn)行的thread移除readylist呢?如果可以,沒(méi)有remove,也就沒(méi)有了insert,沒(méi)有了issue

其實(shí)FreeRTOS使用的就是這種方案:

taskSELECT_HIGHEST_PRIORITY_TASK()


	
  1. #define taskSELECT_HIGHEST_PRIORITY_TASK()

  2. {

  3. UBaseType_t uxTopPriority;

  4. /* Find the highest priority list that contains ready tasks. */

  5. portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );

  6. configASSERT( listCURRENT_LIST_LENGTH(&( pxReadyTasksLists[ uxTopPriority ]))>0);

  7. listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB,&( pxReadyTasksLists[ uxTopPriority ]));

  8. }/* taskSELECT_HIGHEST_PRIORITY_TASK() */

listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )


	
  1. #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )

  2. {

  3. List_t*const pxConstList =( pxList );

  4. /* Increment the index to the next item and return the item, ensuring */

  5. /* we don't return the marker used at the end of the list. */

  6. ( pxConstList )->pxIndex =( pxConstList )->pxIndex->pxNext;

  7. if((void*)( pxConstList )->pxIndex ==(void*)&(( pxConstList )->xListEnd ))

  8. {

  9. ( pxConstList )->pxIndex =( pxConstList )->pxIndex->pxNext;

  10. }

  11. ( pxTCB )=( pxConstList )->pxIndex->pvOwner;

  12. }

可以看出它再獲取最高優(yōu)先級(jí)后,會(huì)直接把readylist的index往后移動(dòng)一次,獲取下一個(gè)任務(wù)。雖然這個(gè)它的時(shí)間片ticks為常量1,但可以給我們提供一個(gè)很好的參考,沒(méi)有remove, insert。筆者之前PR的一個(gè)解決方案

https://github.com/RT-Thread/rt-thread/pull/5954

f4cc5844-f6d0-11ec-ba43-dac502259ad0.gif就是基于該方案。

rt_list_jump_next

雖然rt_list_t也是一個(gè)雙向鏈表,但是少了一個(gè)成員變量index,不能像FreeRTOS那樣直接移動(dòng)完成時(shí)間片的調(diào)度首先我們需要新增一個(gè)rt_list_t操作函數(shù)rt_list_jump_next,完成rt_list_t頭部往后移動(dòng)一次,同時(shí)要保證list成員相對(duì)順序不變

	
  1. /**

  2. * @brief move the list to its next's next position

  3. *

  4. * @param l list to insert it

  5. */

  6. rt_inline void rt_list_jump_next(rt_list_t*l)

  7. {

  8. l->next->prev = l->prev;

  9. l->prev->next= l->next;

  10. l->prev = l->next;

  11. l->next->next->prev = l;

  12. l->next= l->next->next;

  13. l->prev->next= l;

  14. }

大致操作如下:

f62182e6-f6d0-11ec-ba43-dac502259ad0.jpg

這種直接移動(dòng)list head的做法,一次調(diào)度會(huì)有6次指針賦值操作,

而原來(lái)的一次調(diào)度remove(4次), insert(4次)加起來(lái)是8次指針賦值操作

重定義_scheduler_get_highest_priority_thread


	
  1. --- a/src/scheduler.c

  2. +++ b/src/scheduler.c

  3. @@-180,6+180,19@@staticstruct rt_thread* _scheduler_get_highest_priority_thread(rt_ubase_t*high

  4. highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group)-1;

  5. #endif/* RT_THREAD_PRIORITY_MAX > 32 */

  6. +/* if current thread is yield , move the head of priority list to next and change its status to READY */

  7. +if((rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)!=0)

  8. +{

  9. +if(rt_current_thread->tlist.next!= rt_current_thread->tlist.prev)

  10. +{

  11. +/* multiple threads, move the list head to next */

  12. + rt_list_jump_next(&rt_thread_priority_table[rt_current_thread->current_priority]);

  13. +}

  14. +

  15. +/* clear YIELD and ready thread*/

  16. + rt_current_thread->stat = RT_THREAD_READY |(rt_current_thread->stat &~(RT_THREAD_STAT_YIELD_MASK|RT_THREAD_STAT_MASK));

  17. +}

  18. +

  19. /* get highest ready priority thread */

  20. highest_priority_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,

  21. struct rt_thread,

f63490ac-f6d0-11ec-ba43-dac502259ad0.jpg

簡(jiǎn)化rt_schedule

上面的分析可知,rt_schedule之所以搞的復(fù)雜,是因?yàn)楂@取的highest_ready_priority,to_thread不包含對(duì)當(dāng)前正在運(yùn)行thread的計(jì)算。現(xiàn)在我們不把rt_current_thread 移除其readylist,獲得的highest_ready_priority,to_thread就是最終的

	
  1. @@-258,7+271,6@@void rt_system_scheduler_start(void)

  2. rt_current_thread = to_thread;

  3. #endif/* RT_USING_SMP */

  4. - rt_schedule_remove_thread(to_thread);

  5. to_thread->stat = RT_THREAD_RUNNING;

  6. /* switch to new thread */

  7. @@-436,28+448,8@@void rt_schedule(void)

  8. if(rt_thread_ready_priority_group !=0)

  9. {

  10. -/* need_insert_from_thread: need to insert from_thread to ready queue */

  11. -int need_insert_from_thread =0;

  12. -

  13. to_thread = _scheduler_get_highest_priority_thread(&highest_ready_priority);

  14. -if((rt_current_thread->stat & RT_THREAD_STAT_MASK)== RT_THREAD_RUNNING)

  15. -{

  16. -if(rt_current_thread->current_priority < highest_ready_priority)

  17. -{

  18. - to_thread = rt_current_thread;

  19. -}

  20. -elseif(rt_current_thread->current_priority == highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK)==0)

  21. -{

  22. - to_thread = rt_current_thread;

  23. -}

  24. -else

  25. -{

  26. - need_insert_from_thread =1;

  27. -}

  28. - rt_current_thread->stat &=~RT_THREAD_STAT_YIELD_MASK;

  29. -}

  30. -

  31. if(to_thread != rt_current_thread)

  32. {

  33. /* if the destination thread is not the same as current thread */

  34. @@-467,12+459,6@@void rt_schedule(void)

  35. RT_OBJECT_HOOK_CALL(rt_scheduler_hook,(from_thread, to_thread));

  36. -if(need_insert_from_thread)

  37. -{

  38. - rt_schedule_insert_thread(from_thread);

  39. -}

  40. -

  41. - rt_schedule_remove_thread(to_thread);

  42. to_thread->stat = RT_THREAD_RUNNING |(to_thread->stat &~RT_THREAD_STAT_MASK);

  43. /* switch to new thread */

  44. @@-531,7+517,6@@void rt_schedule(void)

  45. }

  46. else

  47. {

  48. - rt_schedule_remove_thread(rt_current_thread);

  49. rt_current_thread->stat = RT_THREAD_RUNNING |(rt_current_thread->stat &~RT_THREAD_STAT_MASK);

  50. }

  51. }

  52. (END)

rt_system_scheduler_start


	
  1. @@-258,7+271,6@@void rt_system_scheduler_start(void)

  2. rt_current_thread = to_thread;

  3. #endif/* RT_USING_SMP */

  4. - rt_schedule_remove_thread(to_thread);

  5. to_thread->stat = RT_THREAD_RUNNING;

  6. /* switch to new thread */

同樣測(cè)試t1 = 5, t2 = 5, t3=2,結(jié)果正常如下:

f6418f00-f6d0-11ec-ba43-dac502259ad0.jpg

審核編輯:湯梓紅


聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    626

    瀏覽量

    28966
  • 調(diào)度算法
    +關(guān)注

    關(guān)注

    1

    文章

    68

    瀏覽量

    11954
  • ISSUE
    +關(guān)注

    關(guān)注

    1

    文章

    5

    瀏覽量

    8081

原文標(biāo)題:關(guān)于時(shí)間片調(diào)度算法issue的分析與解決

文章出處:【微信號(hào):RTThread,微信公眾號(hào):RTThread物聯(lián)網(wǎng)操作系統(tǒng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    FreeRTOS任務(wù)調(diào)度器的三種調(diào)度算法講解(下)

    配置如下時(shí),調(diào)度算法就會(huì)變成不帶時(shí)間的搶占式調(diào)度
    的頭像 發(fā)表于 03-21 13:46 ?1877次閱讀
    FreeRTOS任務(wù)<b class='flag-5'>調(diào)度</b>器的三種<b class='flag-5'>調(diào)度</b><b class='flag-5'>算法</b>講解(下)

    詳解Kernel2.6調(diào)度算法

    Kernel2.6調(diào)度算法仍然是基于優(yōu)先級(jí)的調(diào)度,它的算法復(fù)雜度為O(1),也就是說(shuō)是調(diào)度器的開(kāi)銷是恒定的,與系統(tǒng)當(dāng)前的負(fù)載沒(méi)有關(guān)系。
    發(fā)表于 08-07 06:52

    UCOS III時(shí)間輪轉(zhuǎn)調(diào)度問(wèn)題

    UCOS III時(shí)間輪轉(zhuǎn)調(diào)度,如果時(shí)間到了程序沒(méi)有運(yùn)行完,那下一次進(jìn)入是是否是接著上一次退出的地方繼續(xù)運(yùn)行如果時(shí)間
    發(fā)表于 03-09 04:36

    STM32中基于時(shí)間的任務(wù)調(diào)度框架簡(jiǎn)介

    STM32中基于時(shí)間的任務(wù)調(diào)度框架1.前言:?由于單片機(jī)只能單線程的進(jìn)行工作,只是單純?cè)趙hile循環(huán)中跑程序,導(dǎo)致效率很低,所以采用任務(wù)調(diào)度可以實(shí)現(xiàn)偽多線程工作,任務(wù)
    發(fā)表于 08-24 08:19

    怎樣利用時(shí)間輪轉(zhuǎn)調(diào)度算法去實(shí)現(xiàn)同步時(shí)間調(diào)度的程序呢

    怎樣利用時(shí)間輪轉(zhuǎn)調(diào)度算法去實(shí)現(xiàn)同步時(shí)間調(diào)度的程序呢?
    發(fā)表于 12-20 06:16

    FreeRTOS時(shí)間調(diào)度概述

    一、FreeRTOS時(shí)間調(diào)度概述FreeRTOS支持多個(gè)任務(wù)同時(shí)擁有一個(gè)優(yōu)先級(jí),這些任務(wù)的調(diào)度就可以使用時(shí)間
    發(fā)表于 02-18 06:10

    分析源碼并結(jié)合測(cè)試?yán)陶故静煌闆r下時(shí)間調(diào)度算法issue導(dǎo)致的問(wèn)題及解決辦法

    1、對(duì)時(shí)間調(diào)度算法issue的分析在之前 rt_schedule中need_insert_from_thread的問(wèn)題 提問(wèn)中,筆者提出了
    發(fā)表于 06-28 17:38

    時(shí)間調(diào)度算法issue解決后續(xù)及utest測(cè)試【上】

    1、時(shí)間調(diào)度算法issue解決辦法  之前針對(duì)時(shí)間
    發(fā)表于 11-24 14:47

    時(shí)間調(diào)度算法issue解決后續(xù)及utest測(cè)試【下】

    1、時(shí)間調(diào)度算法issue解決辦法極端情況實(shí)例上面有驚無(wú)險(xiǎn),但是也提示我們考慮極端情況: 同樣是剛延時(shí),未來(lái)得及
    發(fā)表于 11-24 15:58

    基于μC/OS-II的時(shí)間調(diào)度法設(shè)計(jì)方法

    基于μC/OS-II的時(shí)間調(diào)度法設(shè)計(jì)方法 多任務(wù)的調(diào)度算法多種多樣,各種調(diào)度
    發(fā)表于 03-29 15:08 ?1181次閱讀
    基于μC/OS-II的<b class='flag-5'>時(shí)間</b><b class='flag-5'>片</b><b class='flag-5'>調(diào)度</b>法設(shè)計(jì)方法

    UCOS擴(kuò)展例程-UCOSIII時(shí)間輪轉(zhuǎn)調(diào)度

    UCOS擴(kuò)展例程-UCOSIII時(shí)間輪轉(zhuǎn)調(diào)度
    發(fā)表于 12-14 17:24 ?21次下載

    時(shí)間調(diào)度法設(shè)計(jì)方案分析

    ,主要原因如下:①多任務(wù)的時(shí)間調(diào)度在嵌入式領(lǐng)域有實(shí)用價(jià)值。一方面是很多嵌入式軟件系統(tǒng)升級(jí)有這種需求,舊的軟件模塊基于Endless Loop實(shí)現(xiàn),升級(jí)到C/OS-II后,若要最大限度地復(fù)用舊的軟件模塊,
    發(fā)表于 10-19 11:45 ?0次下載
    <b class='flag-5'>時(shí)間</b><b class='flag-5'>片</b><b class='flag-5'>調(diào)度</b>法設(shè)計(jì)方案分析

    UCOIII時(shí)間輪轉(zhuǎn)調(diào)度

    前提:時(shí)間輪轉(zhuǎn)法:主要用于分時(shí)系統(tǒng)中的進(jìn)程調(diào)度。為了實(shí)現(xiàn)輪轉(zhuǎn)調(diào)度,系統(tǒng)把所有就緒進(jìn)程按先入先出的原則排成一個(gè)隊(duì)列的隊(duì)首進(jìn)程,讓它在CPU上運(yùn)行一個(gè)
    發(fā)表于 12-23 19:54 ?1次下載
    UCOIII<b class='flag-5'>時(shí)間</b><b class='flag-5'>片</b>輪轉(zhuǎn)<b class='flag-5'>調(diào)度</b>

    FreeRTOS時(shí)間調(diào)度

    一、FreeRTOS時(shí)間調(diào)度概述FreeRTOS支持多個(gè)任務(wù)同時(shí)擁有一個(gè)優(yōu)先級(jí),這些任務(wù)的調(diào)度就可以使用時(shí)間
    發(fā)表于 12-23 19:57 ?1次下載
    FreeRTOS<b class='flag-5'>時(shí)間</b><b class='flag-5'>片</b><b class='flag-5'>調(diào)度</b>

    FreeRTOS時(shí)間進(jìn)行任務(wù)調(diào)度?

    注意:①任務(wù)切換會(huì)存在時(shí)間開(kāi)銷;FreeRTOS支持時(shí)間,每個(gè)優(yōu)先級(jí)可以支持無(wú)限多個(gè)任務(wù),這些任務(wù)的調(diào)度就是
    發(fā)表于 12-23 20:02 ?0次下載
    FreeRTOS<b class='flag-5'>時(shí)間</b><b class='flag-5'>片</b>進(jìn)行任務(wù)<b class='flag-5'>調(diào)度</b>?