RT-Thread內核實現(四):多優先級

  • 整體思想:
    前幾章並不支持多優先級,都是手動切換指定的線程來運行。支持優先級後,調度器只會找當前最高優先級的線程來運行。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
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

工程文件

按優先級調度

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章