RT_Thread 臨界區訪問控制

一、WHY

   什麼是臨界區,爲什麼我們需要臨界區的訪問控制?首先來看下wiki對臨界區的定義:


在同步的程序設計中,臨界區段(Critical section)指的是一個訪問共享資源(例如:共享設備或是共享存儲器)的程序片段,而這些共享資源無法同時被多個線程訪問的特性。當有線程進入臨界區段時,其他線程或是進程必須等待,以確保這些共享資源是被異或的使用。比如打印機。


二、RT_Thread 臨界區訪問控制機制

2.1 全局中斷:禁止全局中斷,禁止搶佔

全局中斷開關也稱爲中斷鎖,是禁止多線程訪問臨界區最簡單的一種方式,即通過關閉中斷的方式,來保證當前線程不會被其他事件打斷(因爲整個系統已經不再響應那些可以觸發線程重新調度的外部事件),也就是當前線程不會被搶佔,除非這個線程主動放棄了處理器控制權,其函數接口如下:

rt_base_t rt_hw_interrupt_disable(void);
void rt_hw_interrupt_enable(rt_base_t level);

優點:使用中斷鎖來操作臨界區的方法可以應用於任何場合,且其他幾類同步方式都是依賴於中斷鎖而實現的,可以說中斷鎖是最強大的和最高效的同步方法。

缺點:在中斷關閉期間系統將不再響應任何中斷,也就不能響應外部的事件。所以中斷鎖對系統的實時性影響非常巨大,當使用不當的時候會導致系統完全無實時性可言。它屬於粒度最大的訪問控制機制

實現舉例:

/*FILE: libcpu/arm/contex-m4/context_gcc.S*/
/*
 * rt_base_t rt_hw_interrupt_disable();
 */
.global rt_hw_interrupt_disable
.type rt_hw_interrupt_disable, %function
rt_hw_interrupt_disable:
    MRS     r0, PRIMASK
    CPSID   I
    BX      LR

/*
 * void rt_hw_interrupt_enable(rt_base_t level);
 */
.global rt_hw_interrupt_enable
.type rt_hw_interrupt_enable, %function
rt_hw_interrupt_enable:
    MSR     PRIMASK, r0
    BX      LR

2.2 使能全局中斷,禁止搶佔

在這種情況下,打開全局中斷,關閉調度功能,因爲禁止了線程的調度器,所以哪怕是ISR中使一些thread恢復爲ready 狀態,其他線程也仍然不能搶佔當前進程的運行。因此,該機制可以用來禁止多thread間的臨界區同步。

優點:相比2.1,不會禁止全局中斷, ISR仍然可以得到處理。

缺點:因爲禁止了搶佔,因爲ISR資源(鎖,信號量等)釋放而重新ready的高優先級thread並不能搶佔當前的thread,所以仍然會影響到實時性。

代碼實現:

void rt_enter_critical(void)
{
    register rt_base_t level;

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

    /*
     * the maximal number of nest is RT_UINT16_MAX, which is big
     * enough and does not check here
     */
    rt_scheduler_lock_nest ++;

    /* enable interrupt */
    rt_hw_interrupt_enable(level);
}


void rt_exit_critical(void)
{
    register rt_base_t level;

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

    rt_scheduler_lock_nest --;
    if (rt_scheduler_lock_nest <= 0)
    {
        rt_scheduler_lock_nest = 0;
        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        if (rt_current_thread)
        {
            /* if scheduler is started, do a schedule */
            rt_schedule();
        }
    }
    else
    {
        /* enable interrupt */
        rt_hw_interrupt_enable(level);
    }
}

2.3 使能全局中斷,使能搶佔,使用互斥鎖同步

該機制會在臨界區訪問之前去檢查鎖是否有效,如果有效,則上鎖並開始訪問臨界區,訪問結束後釋放鎖;如果無效,則可以阻塞等待該鎖有效。通過該機制,可以保證只有一個thread可以訪問臨界區。

優點:對系統的ISR處理和其他thread影響最小。

缺點:因爲存在着鎖的獲取,釋放,容易出現編程錯誤,產生死鎖。

實現代碼:

/**
 * This function will take a mutex, if the mutex is unavailable, the
 * thread shall wait for a specified time.
 *
 * @param mutex the mutex object
 * @param time the waiting time
 *
 * @return the error code
 */
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
{
    register rt_base_t temp;
    struct rt_thread *thread;

    /* this function must not be used in interrupt even if time = 0 */
    RT_DEBUG_IN_THREAD_CONTEXT; 【1】

    /* 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 ++;   【2】
    }
    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; 【3】
            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 */

                /* 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); 【4】
                }

                /* 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)
                {
                    /* 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)); 【5】
                }

                /* enable interrupt */
                rt_hw_interrupt_enable(temp);

                /* do schedule */
                rt_schedule();     【6】

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

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

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);

    return RT_EOK;
}

【1】:需要保證調用該function的時候處於非中斷上下文狀態;why?

【2】:使用hold 計數來記錄同一個thread佔用鎖的次數,方便同一線程的不同子函數的編程;

【3】【4】:爲了消除優先級反轉的問題,當等待互斥鎖的thread A的優先級比佔用鎖的thread B優先級高,則提高thread B的優先級。

【5】設定等待鎖time out時間並開啓timer

【6】調度調度器

【7】如果當前thread等待鎖被signal 打斷,則再次等待鎖

【8】time out 時間到,退出等待

對應着鎖釋放代碼實現:

/**
 * This function will release a mutex, if there are threads suspended on mutex,
 * it will be waked up.
 *
 * @param mutex the mutex object
 *
 * @return the error code
 */
rt_err_t rt_mutex_release(rt_mutex_t mutex)
{
    register rt_base_t temp;
    struct rt_thread *thread;
    rt_bool_t need_schedule;

    need_schedule = RT_FALSE;

    /* only thread could release mutex because we need test the ownership */
    RT_DEBUG_IN_THREAD_CONTEXT;

    /* 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);

            /* 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;
}

三、總結

     根據不同的鎖需要,採用不同的鎖機制。

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