1. 程式人生 > >RT-Thread核心實現 --優先順序的實現

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)函式。

Created with Raphaël 2.2.0開始優先順序最高為8__rt_ffs結束優先順序最高不大於32__rt_ffs優先順序最高不大於256__rt_ffsyesnoyesnoyes

這個流程圖畫出來,看起來挺醜的。嗯嗯~~不過,是這個意思。所以,在優先順序大於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優先順序的numberhigh_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支援時間片,如果不支援時間片,在同一個優先順序下的兩條執行緒是怎麼進行執行的?

  1. RTThread物聯網作業系統<傳送門> ↩︎