-
整體思想:
前幾章並不支持多優先級,都是手動切換指定的線程來運行。支持優先級後,調度器只會找當前最高優先級的線程來運行。RT-Thread屬於搶佔式實時操作系統,CPU會被當前最高優先級線程搶佔,除非最高優先級線程主動放棄,比如調用rt_thread_delay(rt_tick_t tick)
延時函數,會將線程狀態改爲掛起狀態或者說阻塞狀態,然後執行系統調度。 -
線程控制塊添加了幾屬性
rt_uint8_t current_priority; /* 當前優先級 */
rt_uint8_t init_priority; /* 初始優先級 */
rt_uint32_t number_mask; /* 當前優先級掩碼 */
rt_err_t error; /* 錯誤碼 */
rt_uint8_t stat; /* 線程的狀態 */
- 線程初始化函數增加的部分
rt_err_t rt_thread_init(struct rt_thread *thread,
const char *name,
void (*entry)(void *parameter),
void *parameter,
void *stack_start,
rt_uint32_t stack_size,
rt_uint8_t priority)
{
/*
* 省略...
*/
thread->init_priority = priority;
thread->current_priority = priority;
thread->number_mask = 0;
thread->error = RT_EOK;
thread->stat = RT_THREAD_INIT; // 初始化狀態
return RT_EOK;
}
- 調度器初始化函數
初始化當前優先級(rt_current_priority
)爲空閒線程的優先級,全局變量。
/* 初始化系統調度器 */
void rt_system_scheduler_init(void)
{
register rt_base_t offset;
/* 線程就緒列表初始化 */
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
{
rt_list_init(&rt_thread_priority_table[offset]);
}
/* 初始化當前優先級爲空閒線程的優先級 */
rt_current_priority = RT_THREAD_PRIORITY_MAX - 1;
/* 初始化當前線程控制塊指針 */
rt_current_thread = RT_NULL;
/* 初始化線程就緒優先級組 */
rt_thread_ready_priority_group = 0;
}
- 添加線程啓動函數
啓動一個線程並將其放到系統的就緒列表中,先將線程的狀態爲掛起狀態,然後調用恢復線程函數rt_thread_resume(rt_thread_t thread)
,只有掛起狀態的線程才能被恢復。然後進行系統調度。這裏能看出當前優先級掩碼(thread->number_mask
)的作用,將32位優先級中的對應位置1,在修改此線程優先級組(rt_thread_ready_priority_group
)時比較方便。
rt_err_t rt_thread_startup(rt_thread_t thread)
{
/* 設置當前優先級爲初始優先級 */
thread->current_priority = thread->init_priority;
thread->number_mask = 1L << thread->current_priority;
/* 改變線程的狀態爲掛起狀態 */
thread->stat = RT_THREAD_SUSPEND;
/* 然後恢復線程 */
rt_thread_resume(thread);
if (rt_thread_self() != RT_NULL)
{
/* 系統調度 */
rt_schedule();
}
return RT_EOK;
}
- 線程恢複函數
- 首先判斷目標線程是否是掛起狀態,然後從掛起隊列移除。
- 這裏提到了掛起隊列,應該是指全局變量
rt_list_t rt_thread_defunct;
,這裏沒有用到,在官方源碼中只有線程的退出(rt_thread_exit
)、分離(rt_thread_detach
)和刪除(rt_thread_delete
)操作會將線程掛到rt_thread_defunct
上。 - 然後將線程插入就序列表,
void rt_schedule_insert_thread(struct rt_thread *thread)
,在該函數中會更新線程就緒優先級組(rt_thread_ready_priority_group
)的值。 - 與插入函數對應的有移除函數,
void rt_schedule_remove_thread(struct rt_thread *thread)
,插入操作使用優先級掩碼進行置位,移除操作復位。
rt_err_t rt_thread_resume(rt_thread_t thread)
{
register rt_base_t temp;
/* 將被恢復的線程必須在掛起態,否則返回錯誤碼 */
if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_SUSPEND)
{
return -RT_ERROR;
}
/* 關中斷 */
temp = rt_hw_interrupt_disable();
/* 從掛起隊列移除 */
rt_list_remove(&(thread->tlist));
/* 開中斷 */
rt_hw_interrupt_enable(temp);
/* 插入就緒列表 */
rt_schedule_insert_thread(thread);
return RT_EOK;
}
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);
}
- 空閒函數的初始化也做了相應的修改
void rt_thread_idle_init(void)
{
/* 初始化線程,最低優先級 */
rt_thread_init(&idle,
"idle",
rt_thread_idle_entry,
RT_NULL,
&rt_thread_stack[0],
sizeof(rt_thread_stack),
RT_THREAD_PRIORITY_MAX-1);
rt_thread_startup(&idle);
}
- 啓動系統調度器函數
rt_system_scheduler_start()
之前是手動指定某一個線程,現在是通過__rt_ffs()
獲取當前優先級最高的線程。
/* 啓動系統調度器 */
void rt_system_scheduler_start(void)
{
register struct rt_thread *to_thread;
register rt_ubase_t highest_ready_priority;
/* 獲取就緒的最高優先級 */
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
/* 獲取將要運行線程的線程控制塊 */
to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
rt_current_thread = to_thread;
rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);
}
- 系統調度函數
rt_schedule ()
同樣通過__rt_ffs()
獲取當前優先級最高的線程,然後進行線程切換。
/* 系統調度 */
void rt_schedule(void)
{
rt_base_t level;
register rt_ubase_t highest_ready_priority;
struct rt_thread *to_thread;
struct rt_thread *from_thread;
/* 關中斷 */
level = rt_hw_interrupt_disable();
/* 獲取就緒的最高優先級 */
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
/* 獲取就緒的最高優先級對應的線程控制塊 */
to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
/* 如果目標線程不是當前線程,則要進行線程切換 */
if(to_thread != rt_current_thread){
rt_current_priority = (rt_uint8_t)highest_ready_priority;
from_thread = rt_current_thread;
rt_current_thread = to_thread;
/* 產生上下文切換 */
rt_hw_context_switch((rt_uint32_t)&from_thread->sp, (rt_uint32_t)&to_thread->sp);
rt_hw_interrupt_enable(level);
}else{
rt_hw_interrupt_enable(level);
}
}
- 阻塞延時函數
rt_thread_delay()
先更新剩餘tick數,更新線程狀態,更新全局線程就緒優先級組,然後進行系統調度。高優先級線程可以通過這種方式讓出CPU。
void rt_thread_delay(rt_tick_t tick)
{
register rt_base_t tmp;
struct rt_thread *thread;
tmp = rt_hw_interrupt_disable();
thread = rt_current_thread;
thread->remaining_tick = tick;
/* 改變線程狀態 */
thread->stat = RT_THREAD_SUSPEND;
/* 取反,將對應位清0 */
rt_thread_ready_priority_group &= ~thread->number_mask;
rt_hw_interrupt_enable(tmp);
rt_schedule();
}
- 時基更新函數
rt_tick_increase()
- 該函數是在SysTick中斷服務函數中被調用,功能是更新系統tick數,以及所有線程的
remaining_tick
,如果不爲0,則減1,如果等於0,則更新全局線程就緒優先級組。然後進行系統調度。 - 這裏和PDF中介紹的有些區別,1. 空閒函數的剩餘tick不用判斷;2. 如果
rt_list_isempty(&thread->tlist)
爲真表示該優先級下沒有掛線程,不用更新remaining_tick
。
- 該函數是在SysTick中斷服務函數中被調用,功能是更新系統tick數,以及所有線程的
void rt_tick_increase(void)
{
rt_ubase_t i;
struct rt_thread *thread;
rt_tick ++;
/* 掃描就緒列表中所有線程(idle線程不應該判斷)的remaining_tick,如果不爲0,則減1 */
for(i=0; i < RT_THREAD_PRIORITY_MAX - 1; i++)
{
thread = rt_list_entry( rt_thread_priority_table[i].next,
struct rt_thread,
tlist);
if(rt_list_isempty(&thread->tlist)){
continue;
}
if(thread->remaining_tick > 0)
{
thread->remaining_tick --;
if(thread->remaining_tick == 0){
rt_thread_ready_priority_group |= thread->number_mask;
}
}
}
/* 系統調度 */
rt_schedule();
}
- 主函數
main()
- 可以看到在初始化
rt_flag1_thread
後,就調用了rt_thread_startup(&rt_flag1_thread)
,用來修改線程狀態並啓動啓動該線程,但是該線程並不會立即運行。因爲rt_thread_startup()
中有判斷,當rt_thread_self() != RT_NULL
時纔會進行系統調度。 - 還要注意一點是,main函數第一步就是關閉總中斷,直到啓動系統調度器做第一次線程切換時纔會被打開。
- 可以看到在初始化
int main(){
/* 硬件相關初始化 */
/* 關中斷 */
rt_hw_interrupt_disable();
/* 設置SysTick中斷頻率 25MHz / 100 */
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_thread_init( &rt_flag3_thread,
"rt_flag3_thread", /* 線程名字,字符串形式 */
flag3_thread_entry,
RT_NULL,
&rt_flag3_thread_stack[0],
sizeof(rt_flag3_thread_stack),
4);
// rt_list_insert_before(&(rt_thread_priority_table[2]), &(rt_flag3_thread.tlist));
rt_thread_startup(&rt_flag3_thread);
rt_system_scheduler_start();
}
- 再來看獲取就緒的最高優先級函數
int __rt_ffs(int value)
註釋寫的比較清楚了。
#ifndef RT_USING_CPU_FFS
/*
* __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
};
/**
* 該函數用於從一個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;
/* 檢查 bits [23:16] */
if (value & 0xff0000)
return __lowest_bit_bitmap[(value & 0xff0000) >> 16] + 17;
/* 檢查 bits [31:24] */
return __lowest_bit_bitmap[(value & 0xff000000) >> 24] + 25;
}
#endif