使用STM32編寫一個簡單的RTOS:5.內核同步(二、互斥鎖)


參考資料:RTT官網文檔
關鍵字:分析RT-Thread源碼、stm32、RTOS、互斥量。

互斥量

在其他書籍中的名稱:
mutex :互斥鎖,互斥量,互斥體。

從信號量中我們知道了互斥鎖是特殊的二值信號量,只有0和1兩種狀態。

死鎖
由於互斥鎖只有兩種狀態,開鎖或者關鎖,假如函數A關了鎖後,調用了函數B,函數B也要關鎖,這時就會導致死鎖。

優先級翻轉
使用信號量會導致的另一個潛在問題是線程優先級翻轉問題。所謂優先級翻轉,即當一個高優先級線程試圖通過信號量機制訪問共享資源時,如果該信號量已被一低優先級線程持有,而這個低優先級線程在運行過程中可能又被其它一些中等優先級的線程搶佔,因此造成高優先級線程被許多具有較低優先級的線程阻塞,實時性難以得到保證。如下圖所示:有優先級爲 A、B 和 C 的三個線程,優先級 A> B > C。線程 A,B 處於掛起狀態,等待某一事件觸發,線程 C 正在運行,此時線程 C 開始使用某一共享資源 M。在使用過程中,線程 A 等待的事件到來,線程 A 轉爲就緒態,因爲它比線程 C 優先級高,所以立即執行。但是當線程 A 要使用共享資源 M 時,由於其正在被線程 C 使用,因此線程 A 被掛起切換到線程 C 運行。如果此時線程 B 等待的事件到來,則線程 B 轉爲就緒態。由於線程 B 的優先級比線程 C 高,因此線程 B 開始運行,直到其運行完畢,線程 C 纔開始運行。只有當線程 C 釋放共享資源 M 後,線程 A 才得以執行。在這種情況下,優先級發生了翻轉:線程 B 先於線程 A 運行。這樣便不能保證高優先級線程的響應時間。

在這裏插入圖片描述
在 RT-Thread 操作系統中,互斥量可以解決優先級翻轉問題,實現的是優先級繼承算法。優先級繼承是通過在線程 A 嘗試獲取共享資源而被掛起的期間內,將線程 C 的優先級提升到線程 A 的優先級別,從而解決優先級翻轉引起的問題。這樣能夠防止 C(間接地防止 A)被 B 搶佔,如下圖所示。優先級繼承是指,提高某個佔有某種資源的低優先級線程的優先級,使之與所有等待該資源的線程中優先級最高的那個線程的優先級相等,然後執行,而當這個低優先級線程釋放該資源時,優先級重新回到初始設定。因此,繼承優先級的線程避免了系統資源被任何中間優先級的線程搶佔。

在這裏插入圖片描述
(官網文檔寫的比較詳細,就直接複製過來了)

源碼分析

下面看互斥鎖在RTT中的實現:

struct rt_mutex
{
        struct rt_ipc_object parent;                /* 繼承自 ipc_object 類 */

        rt_uint16_t          value;                   /* 互斥量的值 */
        rt_uint8_t           original_priority;     /* 持有線程的原始優先級 */
        rt_uint8_t           hold;                     /* 持有線程的持有次數   */
        struct rt_thread    *owner;                 /* 當前擁有互斥量的線程 */
 };

還是以分析靜態的初始化爲例。

rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)
{
    RT_ASSERT(mutex != RT_NULL);

    /* init object */
    rt_object_init(&(mutex->parent.parent), RT_Object_Class_Mutex, name);

    /* init ipc object */
    rt_ipc_object_init(&(mutex->parent));

    mutex->value = 1;
    mutex->owner = RT_NULL;
    mutex->original_priority = 0xFF;
    mutex->hold  = 0;

    /* set flag */
    mutex->parent.parent.flag = flag;

    return RT_EOK;
}

首先將互斥鎖加入RT_Object_Class_Mutex類的對象隊列鏈表中,在rt_ipc_object_init中初始化互斥鎖的suspend_thread掛載線程。之後是一些變量的初始化,value初始設置爲了1,即默認處於開鎖狀態。

靜態對應的脫離函數是rt_mutex_detach:

rt_err_t rt_mutex_detach(rt_mutex_t mutex)
{
    RT_ASSERT(mutex != RT_NULL);

    /* wakeup all suspend threads */
    rt_ipc_list_resume_all(&(mutex->parent.suspend_thread));

    /* detach semaphore object */
    rt_object_detach(&(mutex->parent.parent));

    return RT_EOK;
}

先將等待該互斥鎖線程全部喚醒,接着從對象鏈表中移除。

RTT的關鎖函數是rt_mutex_take:

rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
{
    ......
    /* get current thread */
    thread = rt_thread_self();

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();

    /* reset thread error */
    thread->error = RT_EOK;

    if (mutex->owner == thread)
    {
        /* it's the same thread */
        mutex->hold ++;
    }
    else
    {
__again:
        /* The value of mutex is 1 in initial status. Therefore, if the
         * value is great than 0, it indicates the mutex is avaible.
         */
        if (mutex->value > 0)
        {
            /* mutex is available */
            mutex->value --;

            /* set mutex owner and original priority */
            mutex->owner             = thread;
            mutex->original_priority = thread->current_priority;
            mutex->hold ++;
        }
        else
        {
            /* no waiting, return with timeout */
            if (time == 0)
            {
                /* set error as timeout */
                thread->error = -RT_ETIMEOUT;

                /* enable interrupt */
                rt_hw_interrupt_enable(temp);

                return -RT_ETIMEOUT;
            }
            else
            {
                /* mutex is unavailable, push to suspend list */
                RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_take: suspend thread: %s\n",
                                            thread->name));

                /* change the owner thread priority of mutex */
                if (thread->current_priority < mutex->owner->current_priority)
                {
                    /* change the owner thread priority */
                    rt_thread_control(mutex->owner,
                                      RT_THREAD_CTRL_CHANGE_PRIORITY,
                                      &thread->current_priority);
                }

                /* suspend current thread */
                rt_ipc_list_suspend(&(mutex->parent.suspend_thread),
                                    thread,
                                    mutex->parent.parent.flag);

                /* has waiting time, start thread timer */
                if (time > 0)
                {
                    RT_DEBUG_LOG(RT_DEBUG_IPC,
                                 ("mutex_take: start the timer of thread:%s\n",
                                  thread->name));

                    /* reset the timeout of thread timer and start it */
                    rt_timer_control(&(thread->thread_timer),
                                     RT_TIMER_CTRL_SET_TIME,
                                     &time);
                    rt_timer_start(&(thread->thread_timer));
                }

                /* enable interrupt */
                rt_hw_interrupt_enable(temp);

                /* do schedule */
                rt_schedule();

                if (thread->error != RT_EOK)
                {
                	/* interrupt by signal, try it again */
                	if (thread->error == -RT_EINTR) goto __again;

                    /* return error */
                    return thread->error;
                }
                else
                {
                    /* the mutex is taken successfully. */
                    /* disable interrupt */
                    temp = rt_hw_interrupt_disable();
                }
            }
        }
    }
	......
}

參數time的作用和信號量中的作用一樣,設置等待時間,爲0時表示不等待,爲-1時永遠等待。
首先判斷了當前持有該互斥鎖的線程是不是當前線程,是的話hold加1並退出,這樣就不會導致在該函數中重複調用關鎖操作導致死鎖。
如果當前的線程不是互斥鎖的持有者,再判斷互斥鎖是否上鎖,如果沒有上鎖,則將互斥鎖上鎖,即value–,以及互斥鎖的持有者,優先級改爲當前線程及其優先級。
如果互斥鎖已經被其他線程上鎖了,這時判斷如果是不等待則直接退出。如果是等待狀態,則判斷當前的優先級是否比互斥鎖持有者的優先級高,高則將持有線程的優先級設置爲當前的優先級,防止出現優先級翻轉現象。之後將當前線程掛起,並插入互斥鎖的等待鏈表中。接着,如果是永遠等待狀態則不調用定時器,不是則啓動定時器。接着使用調度器調度。
互斥鎖的關鎖和信號量的獲取基本一致,只是多了一步設置鎖持有者的優先級,用來防止優先級翻轉。

接着看開鎖函數rt_mutex_release:

rt_err_t rt_mutex_release(rt_mutex_t mutex)
{
   ......
    need_schedule = RT_FALSE;
    /* get current thread */
    thread = rt_thread_self();

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();

    /* mutex only can be released by owner */
    if (thread != mutex->owner)
    {
        thread->error = -RT_ERROR;

        /* enable interrupt */
        rt_hw_interrupt_enable(temp);

        return -RT_ERROR;
    }

    /* decrease hold */
    mutex->hold --;
    /* if no hold */
    if (mutex->hold == 0)
    {
        /* change the owner thread to original priority */
        if (mutex->original_priority != mutex->owner->current_priority)
        {
            rt_thread_control(mutex->owner,
                              RT_THREAD_CTRL_CHANGE_PRIORITY,
                              &(mutex->original_priority));
        }

        /* wakeup suspended thread */
        if (!rt_list_isempty(&mutex->parent.suspend_thread))
        {
            /* get suspended thread */
            thread = rt_list_entry(mutex->parent.suspend_thread.next,
                                   struct rt_thread,
                                   tlist);

            RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_release: resume thread: %s\n",
                                        thread->name));

            /* set new owner and priority */
            mutex->owner             = thread;
            mutex->original_priority = thread->current_priority;
            mutex->hold ++;

            /* resume thread */
            rt_ipc_list_resume(&(mutex->parent.suspend_thread));

            need_schedule = RT_TRUE;
        }
        else
        {
            /* increase value */
            mutex->value ++;

            /* clear owner */
            mutex->owner             = RT_NULL;
            mutex->original_priority = 0xff;
        }
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);

    /* perform a schedule */
    if (need_schedule == RT_TRUE)
        rt_schedule();

    return RT_EOK;
}

首先判斷當前線程是否是互斥鎖的持有者,如果不是,則直接退出。也就是互斥鎖只能是持有者纔可以開鎖。如果是,則把關鎖次數hold–,如果hold爲0,即開鎖。接着將線程的優先級恢復原來的優先級。然後判斷是否還有其他線程在等待該互斥鎖,如果沒有,則將value++,設置爲開鎖狀態,owner,original_prioriry設置爲缺省值。如果有等待線程,則喚醒第一個線程,這裏不能喚醒所有線程。接着使用調度器調度。
互斥鎖的代碼和信號量的基本一致,只是多了一步設置鎖持有者的優先級,用來防止優先級翻轉。
RTT的互斥鎖就介紹到這裏。

測試

RTT-MINI中的互斥鎖沒有優先級之分,也不能使用遞歸功能。可以看到打印th2之後並沒有直接打印th2 mutex lock,而是打印了th1 mutex unlock。
Alt
在這裏插入圖片描述
測試源碼:https://download.csdn.net/download/u012220052/11236211

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