RT-Thread核心實現 --優先順序的實現
多優先順序是怎麼實現的
多優先順序是怎麼實現的,簡單來說,一個數組就可以組成一個優先順序列表。比如a[10],可以支援10個優先順序。陣列中的每一個元素代表一個優先順序,優先順序就是將一些執行緒分為不同的響應級別,優先順序越高越容易得到執行的可能。這樣理解起來很簡單,但是對於rt_thread來說,應該更精細,更完善。
rt_thread是怎麼實現優先順序的,rt_thread是怎麼將這些優先順序同線程串聯到一起的,這個是我們多優先順序是怎麼實現的所關注的。為了防止大家傻傻分不清我接下來要說的三個表,先在第一個內容下標註出來,來引起大家的注意:
Kenrel下的scheduler.c檔案
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; //執行緒優先順序表 struct rt_thread *rt_current_thread; rt_uint8_t rt_current_priority; #if RT_THREAD_PRIORITY_MAX > 32 //支援大於32個優先順序 /* Maximum priority level, 256 */ rt_uint32_t rt_thread_ready_priority_group; //執行緒就緒優先順序表 rt_uint8_t rt_thread_ready_table[
32]; //二級位圖表 #else /* Maximum priority level, 32 */ //支援小於32個優先順序 rt_uint32_t rt_thread_ready_priority_group; //執行緒就緒優先順序表 #endif
- 第一個表 rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]
你總會看到這個表,這個表記憶體儲的是為不同優先順序設計的連結串列陣列,不同級別的優先順序會在不同的連結串列下,同一級別的優先順序會在相同的連結串列內。具體的樣子<傳送門>- 第二個表 rt_uint32_t rt_thread_ready_priority_group
講道理,你說這是一個表,已經不是特別合適了。因為他的操作已經不是像第一個表一樣,是用陣列一個位元組來表示資訊了;這個表是使用32位的每一位來表示的。一共是32位,用來代表32個優先順序。每一位是一個優先順序,快速查詢優先順序的演算法就是利用這個來實現的。- 第三個表 rt_uint8_t rt_thread_ready_table[32]
通常,我們並不用這個,因為,我們在rtconfig.h中定義RT_THREAD_PRIORITY_MAX通常為32,。但是,實際上,是可以支援到256位的,祕訣就是這個表。這個個第二個表中不一樣,這裡使用rt_uint8_t申明瞭這個陣列,一共是32個元素,每個元素有8位。8*32=256,正好是256個優先順序。RT_Thread實現256個優先順序,是每8位整合為一位,塞到rt_thread_ready_priority_group中,因為這樣可以直接借用__rt_ffs,而不用再重新編寫一個新的點陣圖。
這樣就先簡單提一下,便於後面的深入理解。
__rt_ffs是怎麼快速找到最先轉入就緒態的執行緒的
/**
* This function finds the first bit set (beginning with the least significant bit)
* in value and return the index of that bit.
*
* Bits are numbered starting at 1 (the least significant bit). A return value of
* zero from any of these functions means that the argument was zero.
*
* @return return the index of the first bit set. If value is 0, then this function
* shall return 0.
*/
int __rt_ffs(int value)
{
if (value == 0) return 0;
if (value & 0xff)
return __lowest_bit_bitmap[value & 0xff] + 1;
if (value & 0xff00)
return __lowest_bit_bitmap[(value & 0xff00) >> 8] + 9;
if (value & 0xff0000)
return __lowest_bit_bitmap[(value & 0xff0000) >> 16] + 17;
return __lowest_bit_bitmap[(value & 0xff000000) >> 24] + 25;
}
這裡頻繁呼叫了__lowest_bir_bitmap[],我們看看它是什麼
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
};
這是一個常量表,這裡就沒有運算,就是讀值。這個表的意思就是,你給一個uint8_t型的數,就給你返回這個數的二進位制形態下從最低位數,第一個不為零的位數是哪一位。所以,這個返回的數只可能是0,1,2,3,4,5,6,7。我們從表上看也是這樣的。嗯,高大上的說法是:時間換空間,通俗點說是不用for迴圈來判斷到底是最低的哪一位為1,時間更快,也更穩定。總之的結果就是,對於實時嵌入系統,這樣可以保證時間的準確,不會因為隨機情況而產生時間花費不一致的情況。
const rt_uint8_t __lowest_bit_bitmap[]適用於8位,對於一個32位的數,需要轉換4次。也就是int __rt_ffs(int value)函式。
這個流程圖畫出來,看起來挺醜的。嗯嗯~~不過,是這個意思。所以,在優先順序大於32而小於256的時候,需要rt_uint8_t rt_thread_ready_table[32]
支援最多的優先順序達到256個,怎麼實現的1
下面來看看具體的實現程式碼。
//scheduler.c下的rt_scheduler()函式
register rt_ubase_t highest_ready_priority;
#if RT_THREAD_PRIORITY_MAX <= 32
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#else
register rt_ubase_t number;
number = __rt_ffs(rt_thread_ready_priority_group) - 1;
highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
#endif
register rt_ubase_t number;
number = __rt_ffs(rt_thread_ready_priority_group) - 1;
highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
highest_ready_priority就是返回的一個值,這個值在小於32個優先順序時,是一個小於32的值,代表最高優先順序就緒位。小於32個優先順序的很容易看懂,對於256個優先順序時候,每一句是什麼意思呢?這一句需要看懂,需要看這256個優先順序是怎麼樣轉換的?
//rtdef.h下rt_thread的struct
/* priority */
rt_uint8_t current_priority; /**< current priority */
rt_uint8_t init_priority; /**< initialized priority */
#if RT_THREAD_PRIORITY_MAX > 32
rt_uint8_t number;
rt_uint8_t high_mask;
#endif
rt_uint32_t number_mask;
//thread.c下的_rt_thread_init函式
/* priority init */
RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);
thread->init_priority = priority;
thread->current_priority = priority;
thread->number_mask = 0;
#if RT_THREAD_PRIORITY_MAX > 32
thread->number = 0;
thread->high_mask = 0;
#endif
上面個是線上程控制塊TCB中struct rt_thread中新增支援256優先順序的number和high_mask。並且在rt_thread_init中進行初始化。
//scheduler.c
/**
* This function will start a thread and put it to system ready queue
*
* @param thread the thread to be started
*
* @return the operation status, RT_EOK on OK, -RT_ERROR on error
*/
rt_err_t rt_thread_startup(rt_thread_t thread)
{
/* thread check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
/* set current priority to init priority */
thread->current_priority = thread->init_priority;
/* calculate priority attribute */
#if RT_THREAD_PRIORITY_MAX > 32
thread->number = thread->current_priority >> 3; /* 5bit */
thread->number_mask = 1L << thread->number;
thread->high_mask = 1L << (thread->current_priority & 0x07); /* 3bit */
#else
thread->number_mask = 1L << thread->current_priority;
#endif
RT_DEBUG_LOG(RT_DEBUG_THREAD, ("startup a thread:%s with priority:%d\n",
thread->name, thread->init_priority));
/* change thread stat */
thread->stat = RT_THREAD_SUSPEND;
/* then resume it */
rt_thread_resume(thread);
if (rt_thread_self() != RT_NULL)
{
/* do a scheduling */
rt_schedule();
}
return RT_EOK;
}
thread->number = thread->current_priority >> 3 ,位移操作,向右位移3位,用算術的角度描述是除以8(當然是商部分,不包含餘數的)。記住這個數代表前5位資料,用thread->number來儲存。對於256位的優先順序來說,priority>>3的資料將會是一個小於32的資料,經thread->number_mask = 1L << thread->number運算會儲存一個數據作為number_mask代表高五位,thread->high_mask = 1L << (thread->current_priority & 0x07) 作為低三位。
//scheduler.c
/*
* This function will insert a thread to system ready queue. The state of
* thread will be set as READY and remove from suspend queue.
*
* @param thread the thread to be inserted
* @note Please do not invoke this function in user application.
*/
void rt_schedule_insert_thread(struct rt_thread *thread)
{
register rt_base_t temp;
RT_ASSERT(thread != RT_NULL);
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* change stat */
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));
/* set priority mask */
#if RT_THREAD_PRIORITY_MAX <= 32
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("insert thread[%.*s], the priority: %d\n",
RT_NAME_MAX, thread->name, thread->current_priority));
#else
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
("insert thread[%.*s], the priority: %d 0x%x %d\n",
RT_NAME_MAX,
thread->name,
thread->number,
thread->number_mask,
thread->high_mask));
#endif
#if RT_THREAD_PRIORITY_MAX > 32
rt_thread_ready_table[thread->number] |= thread->high_mask;
#endif
rt_thread_ready_priority_group |= thread->number_mask;
/* enable interrupt */
rt_hw_interrupt_enable(temp);
}
這裡的與位操作,另外,high_mask和number_mask都是被我們儲存過的。至此已經完全加入到就緒列表,經由rt_scheduler()排程,並找出highest_ready_priority,開始進入**rt_thread_priority_table[]找到這條被標記的優先順序下的連結串列,然後經由上下文切換函式rt_hw_context_switch()**切換到需要執行的函式。
對同一優先順序下執行緒切換-時間片思考
看完支援優先順序之後,是不是有一點疑惑,難道同一個優先順序下只有一個執行緒嗎?怎麼保證我切進這個優先順序後能找到我需要的那個函式呢?RT-Thread支援時間片,如果不支援時間片,在同一個優先順序下的兩條執行緒是怎麼進行執行的?