从0到1写RT-Thread内核——支持多优先级
? ? ? ?在本章之前,RT-Thread還沒有支持多優先級,我們手動指定了第一個運行的線程,并在此之后三個線程(包括空閑線程)互相切換,在本章中我們加入優先級的功能,第一個運行的程序是就緒列表里優先級最高的程線程,線程的切換也是切換到已經就緒的線程中優先級最高的一個。
? ? ? ?就緒列表實際上由線程就緒優先級組rt_thread_ready_priority_group和線程優先級表rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]組成,我們在本章之前說的就緒列表是指這里的線程優先級表rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]。
? ? ? ?線程就緒優先級組就是一個32位的整形數,每一個位對應一個優先級,位0對應優先級0,位1對應優先級1,以此類推。比如,當優先級為10的線程已經準備好,那么就將線程就緒優先級組的位10置1,表示線程已經就緒,然后根據10這個索引值,在線程優先級表10(rt_thread_priority_table[10])的這個位置插入線程。
? ? ? ?本章我們為線程控制塊增加與優先級相關的成員,其中還增加了錯誤碼和線程狀態成員,具體見下圖加粗部分:
?
1.線程就緒優先級組
? ? ? ?__rt_ffs函數是用來尋找32位整形數第一個(從低位開始)置1的位號,目的是找出線程就緒優先級組里優先級最高的線程,其代碼如下:
/*** 該函數用于從一個32位的數中尋找第一個被置1的位(從低位開始),* 然后返回該位的索引(即位號) ** @return 返回第一個置1位的索引號。如果全為0,則返回0。 */ int __rt_ffs(int value) {/* 如果值為0,則直接返回0 */if (value == 0) return 0;/* 檢查 bits [07:00] 這里加1的原因是避免當第一個置1的位是位0時返回的索引號與值都為0時返回的索引號重復 */if (value & 0xff)return __lowest_bit_bitmap[value & 0xff] + 1;/* 檢查 bits [15:08] */if (value & 0xff00)return __lowest_bit_bitmap[(value & 0xff00) >> 8] + 9; //9==8+1/* 檢查 bits [23:16] */if (value & 0xff0000)return __lowest_bit_bitmap[(value & 0xff0000) >> 16] + 17; //17==16+1/* 檢查 bits [31:24] */return __lowest_bit_bitmap[(value & 0xff000000) >> 24] + 25; //25==24+1 } /* * __lowest_bit_bitmap[] 數組的解析* 將一個8位整形數的取值范圍0~255作為數組的索引,索引值第一個出現1(從最低位開始)的位號作為該數組索引下的成員值。* 舉例:十進制數10的二進制為:0000 1010,從最低位開始,第一個出現1的位號為bit1,則有__lowest_bit_bitmap[10]=1* 注意:只需要找到第一個出現1的位號即可*/ const rt_uint8_t __lowest_bit_bitmap[] = {/* 00 */ 0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 10 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 20 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 30 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 40 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 50 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 60 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 70 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 80 */ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* 90 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* A0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* B0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* C0 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* D0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* E0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,/* F0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 };2.線程1優先級表
? ? ? ?線程優先級表即我們之前說的就緒列表,每個索引號對應線程的優先級,該索引下維護著一條雙向鏈表,當線程就緒時,線程就會根據優先級插入到對應索引的鏈表,同一個優先級的線程都會被插入到同一條鏈表中(當同一個優先級下有多個線程時,需要時間片的支持,這個在后面章節我們在進一步講解)。
? ? ? ?①將線程插入到線程優先級表和移除分別由rt_schedule_insert_thread()和rt_schedule_remove_thread()這兩個函數實現,它們的具體定義如下:
void rt_schedule_insert_thread(struct rt_thread *thread) {register rt_base_t temp;/* 關中斷 */temp = rt_hw_interrupt_disable();/* 設置線程狀態為就緒態 */thread->stat = RT_THREAD_READY;/* 根據線程的當前優先級將線程插入到就緒列表的優先級表對應的鏈表上 */rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),&(thread->tlist));/* 設置線程就緒優先級組中對應的位 */rt_thread_ready_priority_group |= thread->number_mask;/* 開中斷 */rt_hw_interrupt_enable(temp); }void rt_schedule_remove_thread(struct rt_thread *thread) {register rt_base_t temp;/* 關中斷 */temp = rt_hw_interrupt_disable();/* 將線程從就緒列表刪除 */rt_list_remove(&(thread->tlist));if (rt_list_isempty(&(rt_thread_priority_table[thread->current_priority]))){rt_thread_ready_priority_group &= ~thread->number_mask;}/* 開中斷 */rt_hw_interrupt_enable(temp); }②修改調度器初始化函數rt_system_scheduler_init(),設置當前優先級為空閑線程的優先級(即最低)。
③修改線程初始化函數rt_thread_init()
④添加先啟動函數rt_thread_startup(),該函數設置了當前優先級掩碼并調用了rt_thread_resume函數恢復線程,該函數再調用了rt_schedule_insert_thread函數(該函數把線程設置為就緒態,此前是掛起態,然后根據線程的當前優先級把線程插入到對應的優先級表鏈表并設置對應優先級組的位)
⑤修改空閑線程初始化函數rt_thread_idle_init()
⑥修改啟動系統調度器函數t_system_scheduler_start()
⑦修改系統調度函數rt_schedule()
⑧修改阻塞延時函數rt_thread_delay(),設置掛起時長并把線程狀態設置為掛起態,且把線程就緒優先級組對應位設置為0
⑨修改時基更新函數rt_tick_increase(),如果掛起時長到了,則線程就緒優先級組對應位設置為1(但本章的代碼把線程的狀態改為掛起后好像沒有改回來成就緒,但書里P103提到一點本來應該是把線程從優先級表中移除的,在定時器章節才會講到,如果是移除后重新插入倒是會被設置為就緒態)
3.main函數
/************************************************************************* @brief main函數* @param 無* @retval 無** @attention*********************************************************************** */ int main(void) { /* 硬件初始化 *//* 將硬件相關的初始化放在這里,如果是軟件仿真則沒有相關初始化代碼 *//* 關中斷 */rt_hw_interrupt_disable();/* SysTick中斷頻率設置 */SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );/* 調度器初始化 */rt_system_scheduler_init();/* 初始化空閑線程 */ rt_thread_idle_init(); /* 初始化線程 */rt_thread_init( &rt_flag1_thread, /* 線程控制塊 */"rt_flag1_thread", /* 線程名字,字符串形式 */flag1_thread_entry, /* 線程入口地址 */RT_NULL, /* 線程形參 */&rt_flag1_thread_stack[0], /* 線程棧起始地址 */sizeof(rt_flag1_thread_stack), /* 線程棧大小,單位為字節 */2); /* 優先級 *//* 將線程插入到就緒列表 *///rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );rt_thread_startup(&rt_flag1_thread);/* 初始化線程 */rt_thread_init( &rt_flag2_thread, /* 線程控制塊 */"rt_flag2_thread", /* 線程名字,字符串形式 */flag2_thread_entry, /* 線程入口地址 */RT_NULL, /* 線程形參 */&rt_flag2_thread_stack[0], /* 線程棧起始地址 */sizeof(rt_flag2_thread_stack), /* 線程棧大小,單位為字節 */3); /* 優先級 *//* 將線程插入到就緒列表 *///rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );rt_thread_startup(&rt_flag2_thread);/* 啟動系統調度器 */rt_system_scheduler_start(); } /* 線程1 */ void flag1_thread_entry( void *p_arg ) {for( ;; ){flag1 = 1;rt_thread_delay(2); flag1 = 0;rt_thread_delay(2); } }/* 線程2 */ void flag2_thread_entry( void *p_arg ) {for( ;; ){flag2 = 1;rt_thread_delay(2); flag2 = 0;rt_thread_delay(2); } }void SysTick_Handler(void) {/* 進入中斷 */rt_interrupt_enter();/* 更新時基 */rt_tick_increase();/* 離開中斷 */rt_interrupt_leave(); }4.實驗現象
總結:本章通過在優先級組對應的位置1表示該線程已經就緒,系統調度函數在就緒的線程里面選出優先級最高的線程去執行。我們掛起線程的時候會把線程的狀態改為掛起,并把其在優先級組對應的位置0,當掛起的時間結束后又把相因位設置為1表示線程已經就緒(但本章的代碼把線程的狀態改為掛起后好像沒有改回來成就緒,但書里P103提到一點本來應該是把線程從優先級表中移除的,在定時器章節才會講到,如果是移除后重新插入倒是會被設置為就緒態),等待調度器來調用。本章實驗現象和前一章是一樣的,區別就是本章是優先執行優先級最高的就緒線程,如果該線程被掛起才會去執行比它低優先級的線程。
?
最后聲明一下,我這里只是對學習的知識點進行總結,本文章的大多數知識來自于野火公司出版的《RT-Thread 內核實現與應用開發實戰—基于STM32》,這本書非常不錯,有志學習RT-Thread物聯網操作系統的人可以考慮一下。
總結
以上是生活随笔為你收集整理的从0到1写RT-Thread内核——支持多优先级的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 试管婴儿要取多少卵
- 下一篇: Linux串口阻塞与非阻塞