FreeRTOS 信號(hào)量

@(嵌入式)

Freertos

FreeRtos

簡(jiǎn)述

FreeRTOS 信號(hào)量和互斥鎖是基于隊(duì)列實(shí)現(xiàn)的, 隊(duì)列介紹見(jiàn) << FreeRTOS 消息隊(duì)列 >>依啰。 使用信號(hào)量需要在源文件中包含頭文件 semphr.h , 該文件定義了信號(hào)量的 API, 實(shí)際我們使用的信號(hào)量 API 都是宏定義, 宏的實(shí)際是隊(duì)列提供的函數(shù)。

FreeRTOS 信號(hào)量包括二進(jìn)制信號(hào)量褪测、計(jì)數(shù)信號(hào)量、互斥鎖和遞歸互斥鎖潦刃。 這篇文章介紹如何使用這些信號(hào)量就行任務(wù)間同步以及其實(shí)現(xiàn)侮措。

分析的源碼版本是 v9.0.0


二進(jìn)制信號(hào)量

二進(jìn)制信號(hào)量可以用于互斥和同步, 多用于同步乖杠。 可以把二進(jìn)制信號(hào)量看成一個(gè)深度為1的隊(duì)列(實(shí)際FreeRTOS也是這么實(shí)現(xiàn)的)分扎, 調(diào)用信號(hào)量獲取函數(shù), 設(shè)置阻塞時(shí)間滑黔, 在該時(shí)間內(nèi)沒(méi)有收到信號(hào)笆包, 任務(wù)會(huì)被掛起, 當(dāng)收到信號(hào)或者超時(shí)略荡, 任務(wù)恢復(fù)庵佣,函數(shù)返回。
多個(gè)任務(wù)同時(shí)阻塞在一個(gè)信號(hào)量汛兜, 當(dāng)信號(hào)量有效時(shí)巴粪, 最高優(yōu)先級(jí)的任務(wù)最先解除阻塞。

二進(jìn)制信號(hào)量使用

舉個(gè)使用場(chǎng)景粥谬, 一個(gè)任務(wù)讀取一個(gè)外設(shè)肛根,一直等待外設(shè)可讀占用CPU效率低, 可以調(diào)用信號(hào)量獲取函數(shù)阻塞等待漏策, 當(dāng)外設(shè)可讀派哲,在其中斷函數(shù)中發(fā)送信號(hào)量,喚醒任務(wù)執(zhí)行讀取操作掺喻。
(中斷中必須使用帶有 FromISR結(jié)尾的 API)

semaphore
// 信號(hào)量句柄
SemaphoreHandle_t xSemaphore;

void vATask( void * pvParameters )
{
    // 創(chuàng)建二進(jìn)制信號(hào)量
    xSemaphore = xSemaphoreCreateBinary();

    if( xSemaphore == NULL )
    {
        //heap 空間不夠 芭届,創(chuàng)建失敗
    }
    else
    {
        // 信號(hào)量獲取 設(shè)置阻塞時(shí)間 10 ticks
        if( xSemaphoreTake( xSemaphore, ( TickType_t ) 10 ) == pdTRUE )
        {
            // 獲取到信號(hào)量 储矩! 
            //...
            // 如果任務(wù)中發(fā)送信號(hào)量
            //xSemaphoreGive( xSemaphore );
        }
        else
        {
                // 等待 10 tick 無(wú)法獲取信號(hào)量
                // 超時(shí)返回
        }
    }
}

// 中斷
void vTimerISR( void * pvParameters )
{
    static signed BaseType_t xHigherPriorityTaskWoken;
    xHigherPriorityTaskWoken = pdFALSE;
    // 發(fā)送信號(hào)量
    // 傳遞參數(shù)判斷是否有高優(yōu)先級(jí)任務(wù)就緒
    xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
    // 判斷是否需要觸發(fā)任務(wù)切換
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

如果把信號(hào)量作為互斥鎖使用, 則任務(wù)在獲取信號(hào)后褂乍,處理完畢需要返回持隧。

FreeRTOS 在 8.02 版本提供了一種更加輕量級(jí)的任務(wù)同步, 任務(wù)通知逃片, 由于該方式是集合在任務(wù)控制塊的屡拨,所以不需要額外的內(nèi)存消耗,推薦使用褥实。

二進(jìn)制信號(hào)量實(shí)現(xiàn)

以下看看 FreeRTOS 如何基于隊(duì)列實(shí)現(xiàn)信號(hào)量的呀狼。

創(chuàng)建信號(hào)量

在信號(hào)量定義頭文件可以找到該宏的定義, 可以發(fā)現(xiàn)性锭, 創(chuàng)建一個(gè)信號(hào)量赠潦,實(shí)際上是創(chuàng)建了一個(gè)隊(duì)列, 隊(duì)列深度設(shè)置為1個(gè)草冈, 同時(shí), semSEMAPHORE_QUEUE_ITEM_LENGTH 這個(gè)宏的值為0瓮增, 即 item size 為0怎棱, 表示信號(hào)量這個(gè)隊(duì)列沒(méi)有隊(duì)列項(xiàng)存儲(chǔ)空間, 因?yàn)閷?duì)于信號(hào)量绷跑,沒(méi)有這個(gè)需要拳恋,
對(duì)于二進(jìn)制信號(hào)量, 創(chuàng)建后默認(rèn)初始化其 uxMessageWaiting 為0砸捏, 當(dāng)有信號(hào)發(fā)出時(shí)谬运, 該值變?yōu)?(最大也只能為1),此時(shí)信號(hào)量有效垦藏, 如果有任務(wù)獲取消費(fèi)了信號(hào)量梆暖,該變量再次變?yōu)?, 信號(hào)量無(wú)效掂骏, 有任務(wù)在次調(diào)用獲取信號(hào)量轰驳,可能阻塞等待或者返回信號(hào)量空。

#define xSemaphoreCreateBinary()    \
    xQueueGenericCreate( ( UBaseType_t ) 1, \
        semSEMAPHORE_QUEUE_ITEM_LENGTH, \
        queueQUEUE_TYPE_BINARY_SEMAPHORE )

獲取信號(hào)量

任務(wù)調(diào)用接口獲取信號(hào)量弟灼, 可以通過(guò)如下宏實(shí)現(xiàn) :

#define xSemaphoreTake( xSemaphore, xBlockTime )    \
    xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), \
    NULL,( xBlockTime ), pdFALSE )

這個(gè)函數(shù)是供任務(wù)調(diào)用的级解, 可以看到該函數(shù)實(shí)際上調(diào)用的是隊(duì)列的接收函數(shù), 由于沒(méi)有數(shù)據(jù)可讀田绑, 傳遞的指針為 NULL勤哗。
函數(shù)調(diào)用設(shè)置阻塞時(shí)間,如果調(diào)用函數(shù)時(shí)信號(hào)量無(wú)效掩驱, 則會(huì)阻塞任務(wù)等待芒划。

如果是在中斷中冬竟, 則必須調(diào)用如下宏

#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )\
    xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), \
        NULL, ( pxHigherPriorityTaskWoken ) )

函數(shù) xSemaphoreTakeFromISR是供中斷調(diào)用的,做了中斷優(yōu)先級(jí)處理腊状,并且不會(huì)阻塞诱咏。

釋放信號(hào)量

釋放信號(hào)量的地方可能是中斷,或者是任務(wù)中缴挖, 對(duì)應(yīng)調(diào)用不同接口袋狞。

中斷中釋放

如果在中斷中調(diào)用發(fā)送信號(hào)量, 需要調(diào)用的 API 是 xSemaphoreGiveFromISR映屋, 查看該宏定義如下 :

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )  \
        xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ),\
        ( pxHigherPriorityTaskWoken ) )

該宏實(shí)際調(diào)用的函數(shù) xQueueGiveFromISR定義于 queue.c中苟鸯,
在<< FreeRTOS 消息隊(duì)列 >> 介紹過(guò), 隊(duì)列在中斷中調(diào)用的發(fā)送函數(shù)卻是 xQueueGenericSendFromISR棚点。

對(duì)比了一下兩個(gè)函數(shù)的差別早处, 發(fā)現(xiàn) xQueueGiveFromISR相比隊(duì)列默認(rèn)用的, 差別是沒(méi)有調(diào)用了內(nèi)存拷貝的函數(shù)瘫析,因?yàn)閷?duì)于信號(hào)量而言砌梆, 發(fā)送的消息隊(duì)列不關(guān)心其內(nèi)容,在前面在創(chuàng)建信號(hào)量也提過(guò)贬循, 對(duì)應(yīng)創(chuàng)建的隊(duì)列是沒(méi)有隊(duì)列項(xiàng)存儲(chǔ)空間的咸包, 其 item size 是0。 而主要操作的是變量 uxMessagesWaiting的值杖虾。

簡(jiǎn)化以下該函數(shù) 烂瘫,如下

BaseType_t xQueueGiveFromISR( QueueHandle_t xQueue, 
    BaseType_t * const pxHigherPriorityTaskWoken )
{
    BaseType_t xReturn;
    UBaseType_t uxSavedInterruptStatus;
    Queue_t * const pxQueue = ( Queue_t * ) xQueue;
    
    // 對(duì)應(yīng) item size == 0 的隊(duì)列
    configASSERT( pxQueue->uxItemSize == 0 );

    // 互斥鎖不能在中斷中使用
    configASSERT( !( ( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) && ( pxQueue->pxMutexHolder != NULL ) ) );
    
    // 設(shè)置中斷優(yōu)先級(jí)
    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        // 獲取當(dāng)前隊(duì)列可讀消息數(shù)
        const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
            
        // 如果隊(duì)列仍有空間
        if( uxMessagesWaiting < pxQueue->uxLength )
        {
            const int8_t cTxLock = pxQueue->cTxLock;
            
            // 互斥鎖不能在中斷中使用
            // 不需要考慮互斥鎖類(lèi)型信號(hào)量
            // 增加未讀消息數(shù)量
            pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;

            // 隊(duì)列沒(méi)有被鎖定 喚醒阻塞等待的最高優(yōu)先級(jí)任務(wù)
            if( cTxLock == queueUNLOCKED )
            {
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                {
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                    {
                        if( pxHigherPriorityTaskWoken != NULL )
                        {
                            // 中斷中調(diào)用的這個(gè)函數(shù)名,高優(yōu)先級(jí)任務(wù)就緒
                            // 設(shè)置參數(shù)奇适,表示需要切換任務(wù)
                            *pxHigherPriorityTaskWoken = pdTRUE;
                        }
                    }
                }
            }
            else
            {
                // 如果隊(duì)列被鎖定坟比, 不能在此修改事件鏈表
                // 增加計(jì)數(shù), 解鎖的時(shí)候處理
                pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
            }
            xReturn = pdPASS;
        }
        else
        {
            // 隊(duì)列滿 直接返回
            xReturn = errQUEUE_FULL;
        }
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
    return xReturn;
}
任務(wù)中釋放

在任務(wù)中釋放信號(hào)量調(diào)用的 API 是

#define xSemaphoreGive( xSemaphore )    \
        xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ),\
         NULL, \
         semGIVE_BLOCK_TIME, \
         queueSEND_TO_BACK )

可以看到實(shí)際調(diào)用的函數(shù)是 xQueueGenericSend嚷往, 這個(gè)函數(shù)在隊(duì)列一章做了比較詳細(xì)地介紹過(guò)葛账,此處不貼源碼。
對(duì)于信號(hào)量间影, 由于沒(méi)有消息內(nèi)容注竿, 所以傳遞的指針設(shè)置為 NULL, 不會(huì)執(zhí)行任務(wù)拷貝函數(shù)魂贬,在函數(shù)中判斷隊(duì)列是否滿巩割, 如果沒(méi)有滿, 就增加未讀消息數(shù)的變量付燥, 查看是否有任務(wù)等待信號(hào)而被阻塞宣谈,恢復(fù)最高優(yōu)先級(jí)的任務(wù)。 如果任務(wù)滿键科, 按照設(shè)定的阻塞時(shí)間阻塞掛起任務(wù)等待闻丑。

計(jì)數(shù)信號(hào)量

二進(jìn)制信號(hào)量是長(zhǎng)度為1的隊(duì)列漩怎, 計(jì)數(shù)信號(hào)量則是長(zhǎng)度可以大于1的信號(hào)量, 當(dāng)設(shè)置長(zhǎng)度為1嗦嗡, 其行為和二進(jìn)制型號(hào)量一樣勋锤。
當(dāng)任務(wù)調(diào)用 API 釋放信號(hào)量, 信號(hào)量未讀計(jì)數(shù)加1侥祭, 任務(wù)調(diào)用接收函數(shù)處理信號(hào)量叁执, 則對(duì)應(yīng)減1,初始化信號(hào)量計(jì)數(shù)為0矮冬。
所以谈宛, 使用上, 計(jì)數(shù)信號(hào)量和二進(jìn)制信號(hào)量是差不多胎署。

查看計(jì)數(shù)信號(hào)量創(chuàng)建宏 :

#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )  \
    xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )

相比二進(jìn)制信號(hào)量吆录, 計(jì)數(shù)信號(hào)量創(chuàng)建時(shí)需要設(shè)置兩個(gè)參數(shù),一個(gè)是最大的計(jì)數(shù)值琼牧, 另一個(gè)初始化計(jì)數(shù)值恢筝。
實(shí)際函數(shù)是在隊(duì)列中實(shí)現(xiàn), 對(duì)應(yīng)查看隊(duì)列中該函數(shù)是如何實(shí)現(xiàn)的巨坊, 看到其代碼如下 :

QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
{
    QueueHandle_t xHandle;

    configASSERT( uxMaxCount != 0 );
    configASSERT( uxInitialCount <= uxMaxCount );
    // 申請(qǐng)隊(duì)列滋恬, 深度由第一個(gè)參數(shù)決定
    // item size 也是為 0
    xHandle = xQueueGenericCreate( uxMaxCount, 
        queueSEMAPHORE_QUEUE_ITEM_LENGTH, 
        queueQUEUE_TYPE_COUNTING_SEMAPHORE );

    if( xHandle != NULL )
    {
        // 初始化信號(hào)量計(jì)數(shù)值
        ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
    }
    return xHandle;
}

計(jì)數(shù)型號(hào)量其他地方使用同二進(jìn)制一樣,不繼續(xù)討論抱究。

互斥鎖

當(dāng)一個(gè)任務(wù)訪問(wèn)一個(gè)資源時(shí), 需要獲取令牌带斑, 在其使用期間鼓寺,其他任務(wù)不能使用該資源, 使用完后勋磕, 釋放令牌妈候, 其他任務(wù)可以訪問(wèn), 保證資源在一段時(shí)間只能由一個(gè)任務(wù)讀取修改挂滓。

資源互斥

與二進(jìn)制信號(hào)量最大的不同在于苦银, 互斥信號(hào)量帶有優(yōu)先級(jí)繼承的機(jī)制,這個(gè)機(jī)制用于減低優(yōu)先級(jí)反轉(zhuǎn)的影響赶站。
舉個(gè)例子幔虏, 三個(gè)任務(wù)優(yōu)先級(jí)從大到小依次 A > B > C, 某種情況下, 任務(wù)C 獲取了互斥鎖贝椿, 之后任務(wù)A 請(qǐng)求拿鎖被掛起想括, 任務(wù)C 繼續(xù)運(yùn)行, 如果沒(méi)有優(yōu)先級(jí)繼承烙博, 任務(wù)B 就緒瑟蜈,由于優(yōu)先級(jí)高于當(dāng)前的任務(wù)C烟逊, 所以開(kāi)始運(yùn)行, 這樣導(dǎo)致任務(wù)C 無(wú)法及時(shí)放鎖铺根,進(jìn)而導(dǎo)致任務(wù)A 無(wú)法運(yùn)行宪躯, 但是任務(wù)A 的優(yōu)先級(jí)比B 高, 這就是優(yōu)先級(jí)反轉(zhuǎn)位迂。
如果加入優(yōu)先級(jí)繼承访雪, 任務(wù)C 拿鎖, 任務(wù)A 請(qǐng)求拿鎖被掛起時(shí)囤官, 由于C < A, 通過(guò)繼承機(jī)制冬阳, 提高C 的優(yōu)先級(jí),使其等于 A党饮, 這樣肝陪, 以上任務(wù)B 就無(wú)法搶占C, 任務(wù)C 結(jié)束釋放鎖讓后恢復(fù)其本來(lái)優(yōu)先級(jí)刑顺, 任務(wù)A 開(kāi)始運(yùn)行氯窍。

創(chuàng)建互斥信號(hào)量

使用互測(cè)鎖前需要?jiǎng)?chuàng)建互斥鎖, 需要調(diào)用 API 的定義 :

#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

查找對(duì)應(yīng)的實(shí)現(xiàn)函數(shù) xQueueCreateMutex源碼 蹲堂, 函數(shù)調(diào)用了隊(duì)列創(chuàng)建函數(shù)創(chuàng)建了一個(gè)深度為1狼讨, item size 為 0 的隊(duì)列, 到這里看起來(lái)和二進(jìn)制信號(hào)量一樣柒竞。
隊(duì)列創(chuàng)建后政供,調(diào)用了專(zhuān)門(mén)初始化互斥信號(hào)量的函數(shù)初始化。

QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
    Queue_t *pxNewQueue;
    const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;

    pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
    prvInitialiseMutex( pxNewQueue );
    return pxNewQueue;
}

初始化互斥信號(hào)量朽基,

static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
    if( pxNewQueue != NULL )
    {
        // 初始化互斥信號(hào)量的參數(shù)
        // 互斥信號(hào)量當(dāng)前有效
        pxNewQueue->pxMutexHolder = NULL;
        // 標(biāo)記類(lèi)型
        pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;

        // 遞歸信號(hào)量用
        pxNewQueue->u.uxRecursiveCallCount = 0;
        
        // 發(fā)送一個(gè)消息到隊(duì)列
        // 這樣布隔,第一個(gè)拿信號(hào)量的任務(wù)不會(huì)被掛起
        ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
    }
}

拿鎖

任務(wù)拿鎖可以通過(guò)調(diào)用 API 定義如下

#define xSemaphoreTake( xSemaphore, xBlockTime )    \
    xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )

如果任務(wù)調(diào)用此函數(shù)時(shí)互斥鎖有效,則拿到鎖后返回稼虎。
這個(gè)函數(shù)在隊(duì)列文章中介紹過(guò)衅檀,該函數(shù)中, 對(duì)于互斥鎖有一些特殊處理霎俩,主要是實(shí)現(xiàn)了優(yōu)先級(jí)繼承機(jī)制哀军。
在一個(gè)任務(wù)拿鎖后, 其他任務(wù)嘗試拿鎖失敗打却,如果設(shè)置了阻塞時(shí)間杉适,則該任務(wù)會(huì)被阻塞,在進(jìn)入阻塞前学密, 函數(shù)會(huì)判斷當(dāng)前任務(wù)的優(yōu)先級(jí)是否高于擁有鎖任務(wù)的優(yōu)先級(jí)淘衙,如果高于, 則會(huì)先提高擁有鎖任務(wù)的優(yōu)先級(jí)腻暮。
實(shí)現(xiàn)的代碼如下
點(diǎn)擊源碼

#if ( configUSE_MUTEXES == 1 )
{
    if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
    {
        taskENTER_CRITICAL();
        {
            // 判斷是否需要修改拿鎖任務(wù)優(yōu)先級(jí)
            vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
        }
        taskEXIT_CRITICAL();
    }
}
#endif  

查看 vTaskPriorityInherit 函數(shù)的實(shí)現(xiàn)彤守, 該函數(shù)比較當(dāng)前任務(wù)(拿鎖失敗毯侦,阻塞等待)和拿鎖任務(wù)優(yōu)先級(jí), 進(jìn)行優(yōu)先級(jí)繼承處理具垫。

void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
    TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;

    // 判斷是否有任務(wù)拿著鎖
    if( pxMutexHolder != NULL )
    {
        // 判斷當(dāng)前任務(wù)的優(yōu)先級(jí)是否比拿鎖任務(wù)高
        if( pxTCB->uxPriority < pxCurrentTCB->uxPriority )
        {
            // 修改拿鎖任務(wù)在事件鏈表項(xiàng)的優(yōu)先級(jí)值
            if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) &
                 taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
            {
                listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), 
                    ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority );
            }

            //如果拿鎖任務(wù)在就緒鏈表侈离, 移動(dòng)拿鎖任務(wù)到新優(yōu)先級(jí)任務(wù)鏈表
            if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxTCB->uxPriority ] ), 
                &( pxTCB->xStateListItem ) ) != pdFALSE )
            {
                // 先從舊就緒鏈表移除
                if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
                {
                    taskRESET_READY_PRIORITY( pxTCB->uxPriority );
                }

                // 修改優(yōu)先級(jí), 優(yōu)先級(jí)繼承筝蚕, 并加入到新的優(yōu)先級(jí)就緒鏈表
                pxTCB->uxPriority = pxCurrentTCB->uxPriority;
                prvAddTaskToReadyList( pxTCB );
            }
            else
            {
                // 如果拿鎖任務(wù)沒(méi)有在就緒鏈表卦碾, 直接修改優(yōu)先級(jí)值即可
                pxTCB->uxPriority = pxCurrentTCB->uxPriority;
            }
        }
    }
}

互斥鎖不能在中斷使用, 因?yàn)橹袛嗪瘮?shù)沒(méi)有優(yōu)先級(jí)繼承起宽,同時(shí)洲胖, 中斷函數(shù)不能阻塞。

放鎖

任務(wù)使用資源后坯沪, 需要釋放互斥鎖绿映,這樣其他任務(wù)才能正常拿鎖使用資源。
釋放鎖的接口定義如下 :

#define xSemaphoreGive( xSemaphore )    \
    xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

宏實(shí)際調(diào)用的函數(shù)實(shí)際也解析過(guò)了腐晾, 上面提到拿鎖的時(shí)候叉弦, 任務(wù)堵塞會(huì)進(jìn)行優(yōu)先級(jí)繼承處理, 因此藻糖,當(dāng)一個(gè)任務(wù)獲取了互斥鎖淹冰, 在其使用期間, 其本身優(yōu)先級(jí)可能已經(jīng)被提高過(guò)了巨柒, 當(dāng)其釋放鎖的時(shí)候樱拴, 需要恢復(fù)到原來(lái)的優(yōu)先級(jí)。還鎖的操作是向隊(duì)列發(fā)送返回一個(gè)消息洋满,在拷貝消息內(nèi)容的函數(shù)疹鳄,判斷隊(duì)列是互斥鎖時(shí), 會(huì)調(diào)用優(yōu)先級(jí)繼承解除函數(shù)芦岂, 恢復(fù)任務(wù)的優(yōu)先級(jí)。
優(yōu)先級(jí)恢復(fù)函數(shù)如下垫蛆,

BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
    TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
    BaseType_t xReturn = pdFALSE;

    if( pxMutexHolder != NULL )
    {
        // 當(dāng)前任務(wù)是拿鎖任務(wù)
        // 這個(gè)隊(duì)列是互斥鎖類(lèi)型
        configASSERT( pxTCB == pxCurrentTCB );
        configASSERT( pxTCB->uxMutexesHeld );
        ( pxTCB->uxMutexesHeld )--;

        // 拿鎖任務(wù)的優(yōu)先級(jí)被修改了
        if( pxTCB->uxPriority != pxTCB->uxBasePriority )
        {
            // 鎖徹底釋放
            if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )
            {
                // 從鏈表移除任務(wù)
                if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
                {
                    taskRESET_READY_PRIORITY( pxTCB->uxPriority );
                }

                // 恢復(fù)優(yōu)先級(jí) 
                pxTCB->uxPriority = pxTCB->uxBasePriority;

                // 恢復(fù)任務(wù)TCB 其他和優(yōu)先級(jí)相關(guān)的參數(shù)
                listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority );
                // 重新插入到就緒鏈表
                prvAddTaskToReadyList( pxTCB );

                // 發(fā)生優(yōu)先級(jí)繼承
                // 說(shuō)明有高優(yōu)先級(jí)等鎖禽最, 所以提示需要任務(wù)切換
                xReturn = pdTRUE;
            }
        }
    }
    return xReturn;
}

任務(wù)發(fā)生優(yōu)先級(jí)繼承, 本身優(yōu)先級(jí)被修改提高袱饭,任務(wù)原始優(yōu)先級(jí)會(huì)保存在 pxTCB->uxBasePriority中川无, 恢復(fù)的時(shí)候使用。
優(yōu)先級(jí)繼承恢復(fù)后虑乖, 可知有更高優(yōu)先級(jí)任務(wù)阻塞等待鎖懦趋,所以需要返回提示任務(wù)切換, 對(duì)應(yīng)隊(duì)列發(fā)送函數(shù)中的特殊處理一段 , 根據(jù)內(nèi)存拷貝函數(shù) prvCopyDataToQueue 返回值判斷是否需要觸發(fā)任務(wù)切換疹味, 因?yàn)槿蝿?wù)拷貝函數(shù)調(diào)用了上面這個(gè)函數(shù)恢復(fù)優(yōu)先級(jí)仅叫,需不需要觸發(fā)任務(wù)切換的返回值就是由這個(gè)函數(shù)提供的帜篇。

遞歸互斥鎖

獲取遞歸互斥量的任務(wù)可以重復(fù)獲取該遞歸互斥量。使用xSemaphoreTakeRecursive() 函數(shù)成功獲取幾次遞歸互斥量诫咱,對(duì)應(yīng)的就要使用xSemaphoreGiveRecursive()函數(shù)返還幾次笙隙,在此之前遞歸互斥量都處于無(wú)效狀態(tài), 其他任務(wù)無(wú)法獲取坎缭, 必須等待獲取的任務(wù)釋放完畢竟痰。

遞歸互斥鎖創(chuàng)建調(diào)用接口 :

#define xSemaphoreCreateRecursiveMutex() \
    xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )

實(shí)際調(diào)用函數(shù)同普通互斥鎖一樣。

獲取遞歸信號(hào)量

遞歸信號(hào)量在同一個(gè)任務(wù)可以多次拿取掏呼, 其調(diào)用的接口不同其他信號(hào)量的 xSemaphoreTake坏快, 而是如下宏 :

#define xSemaphoreTakeRecursive( xMutex, xBlockTime )   \
    xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )

查看具體實(shí)現(xiàn)函數(shù) xQueueTakeMutexRecursive, 看看有什么不同憎夷。

BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_t xTicksToWait )
{
    BaseType_t xReturn;
    Queue_t * const pxMutex = ( Queue_t * ) xMutex;

    configASSERT( pxMutex );

    if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() )
    {
        // 如果嘗試拿鎖的任務(wù)是當(dāng)前拿了鎖的任務(wù)莽鸿, 則遞增拿鎖次鎖
        ( pxMutex->u.uxRecursiveCallCount )++;
        xReturn = pdPASS;
    }
    else
    {
        // 其他任務(wù)嘗試拿鎖, 同使用普通互斥鎖一樣
        xReturn = xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE );

        // 阻塞等待
        // 如果恢復(fù)后岭接,在超時(shí)時(shí)間內(nèi)拿到鎖
        // 遞增計(jì)數(shù)富拗, 第一次拿鎖!鸣戴!
        if( xReturn != pdFAIL )
        {
            ( pxMutex->u.uxRecursiveCallCount )++;
        }
    }
    return xReturn;
}

相比普通互斥鎖啃沪,遞歸互斥鎖允許同一個(gè)任務(wù)多次拿鎖, 所以其拿鎖接口會(huì)判斷拿鎖任務(wù)是否是擁有鎖的任務(wù)窄锅,如果是创千, 則遞增拿鎖次數(shù), 其他任務(wù)處理則和普通互斥鎖一樣入偷,阻塞等待追驴。

釋放遞歸信號(hào)量

響應(yīng)的, 遞歸互斥鎖釋放調(diào)用的接口定義 :

#define xSemaphoreGiveRecursive( xMutex )   \
    xQueueGiveMutexRecursive( ( xMutex ) )
#endif

查看實(shí)際實(shí)現(xiàn)的函數(shù) :

BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
{
    BaseType_t xReturn;
    Queue_t * const pxMutex = ( Queue_t * ) xMutex;

    configASSERT( pxMutex );

    // 判斷嘗試放鎖的任務(wù)是否拿著鎖
    if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() )
    {

        // 遞歸互斥鎖疏之, 拿幾個(gè)需要對(duì)應(yīng)放幾個(gè)
        ( pxMutex->u.uxRecursiveCallCount )--;

        // 計(jì)數(shù)清零殿雪,說(shuō)明可以真正放鎖
        if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 )
        {
            // 返回鎖
            ( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
        }
        xReturn = pdPASS;
    }
    else
    {
        // 當(dāng)前任務(wù)不是持有鎖的任務(wù), 無(wú)法釋放
        xReturn = pdFAIL;
    }

    // 還不能真正放鎖
    return xReturn;
}

對(duì)應(yīng)拿鎖的處理锋爪, 以上的函數(shù)也就很好理解了丙曙。

FreeRTOS 信號(hào)量記錄到此結(jié)束。


參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末其骄,一起剝皮案震驚了整個(gè)濱河市亏镰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拯爽,老刑警劉巖索抓,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡逼肯,警方通過(guò)查閱死者的電腦和手機(jī)耸黑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)汉矿,“玉大人崎坊,你說(shuō)我怎么就攤上這事≈弈矗” “怎么了奈揍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)赋续。 經(jīng)常有香客問(wèn)我男翰,道長(zhǎng),這世上最難降的妖魔是什么纽乱? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任蛾绎,我火速辦了婚禮,結(jié)果婚禮上鸦列,老公的妹妹穿的比我還像新娘租冠。我一直安慰自己,他們只是感情好薯嗤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布顽爹。 她就那樣靜靜地躺著,像睡著了一般骆姐。 火紅的嫁衣襯著肌膚如雪镜粤。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天玻褪,我揣著相機(jī)與錄音肉渴,去河邊找鬼。 笑死带射,一個(gè)胖子當(dāng)著我的面吹牛同规,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播窟社,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼捻浦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了桥爽?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤昧识,失蹤者是張志新(化名)和其女友劉穎钠四,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缀去,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年侣灶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缕碎。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡褥影,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咏雌,到底是詐尸還是另有隱情凡怎,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布赊抖,位于F島的核電站统倒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏氛雪。R本人自食惡果不足惜房匆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望报亩。 院中可真熱鬧浴鸿,春花似錦、人聲如沸弦追。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)骗卜。三九已至宠页,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寇仓,已是汗流浹背举户。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遍烦,地道東北人俭嘁。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像服猪,于是被迫代替她去往敵國(guó)和親供填。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容