FreeRTOS進(jìn)階信號(hào)量示例的完全解析
前言
FreeRTOS的信號(hào)量包括二進(jìn)制信號(hào)量、計(jì)數(shù)信號(hào)量、互斥信號(hào)量(以后簡稱互斥量)和遞歸互斥信號(hào)量(以后簡稱遞歸互斥量)。關(guān)于它們的區(qū)別可以參考FreeRTOS進(jìn)階信號(hào)量示例一文。
信號(hào)量API函數(shù)實(shí)際上都是宏,它使用現(xiàn)有的隊(duì)列機(jī)制。這些宏定義在semphr.h文件中。如果使用信號(hào)量或者互斥量,需要包含semphr.h頭文件。
二進(jìn)制信號(hào)量、計(jì)數(shù)信號(hào)量和互斥量信號(hào)量的創(chuàng)建API函數(shù)是獨(dú)立的,但是獲取和釋放API函數(shù)都是相同的;遞歸互斥信號(hào)量的創(chuàng)建、獲取和釋放API函數(shù)都是獨(dú)立的。
1.信號(hào)量創(chuàng)建
在FreeRTOS進(jìn)階之隊(duì)列示例分析中,我們分析了隊(duì)列的實(shí)現(xiàn)過程,包括隊(duì)列創(chuàng)建、入隊(duì)和出隊(duì)操作。在那篇文章中我們說過,創(chuàng)建隊(duì)列API函數(shù)實(shí)際是調(diào)用通用隊(duì)列創(chuàng)建函數(shù)xQueueGenericCreate()來實(shí)現(xiàn)的。其實(shí),不但創(chuàng)建隊(duì)列實(shí)際調(diào)用通用隊(duì)列創(chuàng)建函數(shù),二進(jìn)制信號(hào)量、計(jì)數(shù)信號(hào)量、互斥量和遞歸互斥量也都直接或間接使用這個(gè)函數(shù),如表1-1所示。表1-1中紅色字體表示是間接調(diào)用xQueueGenericCreate()函數(shù)。
表1-1:隊(duì)列、信號(hào)量和互斥量創(chuàng)建宏與直接(間接)執(zhí)行函數(shù)
1.1.創(chuàng)建二進(jìn)制信號(hào)量
二進(jìn)制信號(hào)量創(chuàng)建實(shí)際上是直接使用通用隊(duì)列創(chuàng)建函數(shù)xQueueGenericCreate()。創(chuàng)建二進(jìn)制信號(hào)量API接口實(shí)際上是一個(gè)宏,定義如下:
#define xSemaphoreCreateBinary() \ xQueueGenericCreate( \ ( UBaseType_t ) 1, \ semSEMAPHORE_QUEUE_ITEM_LENGTH, \ NULL, \ NULL, \ queueQUEUE_TYPE_BINARY_SEMAPHORE\ )
通過這個(gè)宏定義我們知道創(chuàng)建二進(jìn)制信號(hào)量實(shí)際上是創(chuàng)建了一個(gè)隊(duì)列,隊(duì)列項(xiàng)有1個(gè),但是隊(duì)列項(xiàng)的大小為0(宏semSEMAPHORE_QUEUE_ITEM_LENGTH定義為0)。
有了隊(duì)列創(chuàng)建的知識(shí),我們可以很容易的畫出初始化后的二進(jìn)制信號(hào)量內(nèi)存,如圖1-1所示。
圖1-1:初始化后的二進(jìn)制信號(hào)量對(duì)象內(nèi)存
或許不止一人像我一樣奇怪,創(chuàng)建一個(gè)沒有隊(duì)列項(xiàng)存儲(chǔ)空間的隊(duì)列,信號(hào)量用什么表示?其實(shí)二進(jìn)制信號(hào)量的釋放和獲取都是通過操作隊(duì)列結(jié)構(gòu)體成員uxMessageWaiting來實(shí)現(xiàn)的(圖1-1紅色部分,uxMessageWaiting表示隊(duì)列中當(dāng)前隊(duì)列項(xiàng)的個(gè)數(shù))。經(jīng)過初始化后,變量uxMessageWaiting為0,這說明隊(duì)列為空,也就是信號(hào)量處于無效狀態(tài)。在使用API函數(shù)xSemaphoreTake()獲取信號(hào)之前,需要先釋放一個(gè)信號(hào)量。后面講到二進(jìn)制信號(hào)量釋放和獲取時(shí)還會(huì)詳細(xì)介紹。
1.2.創(chuàng)建計(jì)數(shù)信號(hào)量
創(chuàng)建計(jì)數(shù)信號(hào)量間接使用通用隊(duì)列創(chuàng)建函數(shù)xQueueGenericCreate()。創(chuàng)建計(jì)數(shù)信號(hào)量API接口同樣是個(gè)宏定義:
#define xSemaphoreCreateCounting(uxMaxCount, uxInitialCount ) \ xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ), (NULL ) )
創(chuàng)建計(jì)數(shù)信號(hào)量API接口有兩個(gè)參數(shù),含義如下:
uxMaxCount
:最大計(jì)數(shù)值,當(dāng)信號(hào)到達(dá)這個(gè)值后,就不再增長了。
uxInitialCount
:創(chuàng)建信號(hào)量時(shí)的初始值。
我們來看一下函數(shù)xQueueCreateCountingSemaphore()如何實(shí)現(xiàn)的:
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_tuxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t *pxStaticQueue ) { QueueHandle_t xHandle; configASSERT( uxMaxCount != 0 ); configASSERT( uxInitialCount <= uxMaxCount ); /*調(diào)用通用隊(duì)列創(chuàng)建函數(shù)*/ xHandle =xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticQueue, queueQUEUE_TYPE_COUNTING_SEMAPHORE ); if( xHandle != NULL ) { ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount; } configASSERT( xHandle ); return xHandle; }
從代碼可以看出,創(chuàng)建計(jì)數(shù)信號(hào)量仍然調(diào)用通用隊(duì)列創(chuàng)建函數(shù)xQueueGenericCreate()來創(chuàng)建一個(gè)隊(duì)列,隊(duì)列項(xiàng)的數(shù)目由參數(shù)uxMaxCount指定,每個(gè)隊(duì)列項(xiàng)的大小由宏queueSEMAPHORE_QUEUE_ITEM_LENGTH指出,我們找到這個(gè)宏定義發(fā)現(xiàn),這個(gè)宏被定義為0,也就是說創(chuàng)建的隊(duì)列只有隊(duì)列數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)空間而沒有隊(duì)列項(xiàng)存儲(chǔ)空間。
如果隊(duì)列創(chuàng)建成功,則將隊(duì)列結(jié)構(gòu)體成員uxMessageWaiting設(shè)置為初始計(jì)數(shù)信號(hào)量值。初始化后的計(jì)數(shù)信號(hào)量內(nèi)存如圖3-1所示。
圖1-2:初始化后的計(jì)數(shù)信號(hào)量對(duì)象內(nèi)存
1.3創(chuàng)建互斥量
創(chuàng)建互斥量間接使用通用隊(duì)列創(chuàng)建函數(shù)xQueueGenericCreate()。創(chuàng)建互斥量API接口同樣是個(gè)宏,定義如下:
#define xSemaphoreCreateMutex() \ xQueueCreateMutex( queueQUEUE_TYPE_MUTEX, NULL )
其中,宏queueQUEUE_TYPE_MUTEX用于通用隊(duì)列創(chuàng)建函數(shù),表示創(chuàng)建隊(duì)列的類型是互斥量,在文章FreeRTOS進(jìn)階之隊(duì)列示例分析關(guān)于通用隊(duì)列創(chuàng)建函數(shù)參數(shù)說明中提到了這個(gè)宏。
我們來看一下函數(shù)xQueueCreateMutex()是如何實(shí)現(xiàn)的:
#if ( configUSE_MUTEXES == 1 ) QueueHandle_t xQueueCreateMutex( const uint8_tucQueueType, StaticQueue_t *pxStaticQueue ) { Queue_t *pxNewQueue; const UBaseType_tuxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0; /* 防止編譯器產(chǎn)生警告信息 */ ( void ) ucQueueType; /*調(diào)用通用隊(duì)列創(chuàng)建函數(shù)*/ pxNewQueue = ( Queue_t * )xQueueGenericCreate( uxMutexLength, uxMutexSize, NULL, pxStaticQueue, ucQueueType ); /* 成功分配新的隊(duì)列結(jié)構(gòu)體? */ if( pxNewQueue != NULL ) { /*xQueueGenericCreate()函數(shù)會(huì)按照通用隊(duì)列的方式設(shè)置所有隊(duì)列結(jié)構(gòu)體成員,但是我們是要?jiǎng)?chuàng)建互斥量.因此需要對(duì)一些結(jié)構(gòu)體成員重新賦值. */ pxNewQueue->pxMutexHolder = NULL; pxNewQueue->uxQueueType =queueQUEUE_IS_MUTEX; //NULL /* 用于遞歸互斥量創(chuàng)建 */ pxNewQueue->u.uxRecursiveCallCount = 0; /* 使用一個(gè)預(yù)期狀態(tài)啟動(dòng)信號(hào)量 */ ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK); } return pxNewQueue; } #endif /* configUSE_MUTEXES */
這個(gè)函數(shù)是帶條件編譯的,只有將宏configUSE_MUTEXES定義為1才會(huì)編譯這個(gè)函數(shù)。
函數(shù)首先調(diào)用通用隊(duì)列創(chuàng)建函數(shù)xQueueGenericCreate()來創(chuàng)建一個(gè)隊(duì)列,隊(duì)列項(xiàng)數(shù)目為1,隊(duì)列項(xiàng)大小為0,說明創(chuàng)建的隊(duì)列只有隊(duì)列數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)空間而沒有隊(duì)列項(xiàng)存儲(chǔ)空間。
如果隊(duì)列創(chuàng)建成功,通用隊(duì)列創(chuàng)建函數(shù)還會(huì)按照通用隊(duì)列的方式 初始化所有隊(duì)列結(jié)構(gòu)體成員。但是這里要?jiǎng)?chuàng)建的是互斥量,所以有一些結(jié)構(gòu)體成員必須重新賦值。在這段代碼中,可能你會(huì)疑惑,隊(duì)列結(jié)構(gòu)體成員中,并沒有pxMutexHolder和uxQueueType!其實(shí)這兩個(gè)標(biāo)識(shí)符只是宏定義,是專門為互斥量而定義的,如下所示:
#define pxMutexHolder pcTail #define uxQueueType pcHead #define queueQUEUE_IS_MUTEX NULL
當(dāng)隊(duì)列結(jié)構(gòu)體用于互斥量時(shí),成員pcHead和pcTail指針就不再需要,并且將pcHead指針設(shè)置為NULL,表示pcTail指針實(shí)際指向互斥量持有者任務(wù)TCB(如果有的話)。
最后調(diào)用函數(shù)xQueueGenericSend()釋放一個(gè)互斥量,相當(dāng)于互斥量創(chuàng)建后是有效的,可以直接使用獲取信號(hào)量API函數(shù)來獲取這個(gè)互斥量。如果某資源同時(shí)只準(zhǔn)一個(gè)任務(wù)訪問,可以用互斥量保護(hù)這個(gè)資源。這個(gè)資源一定是存在的,所以創(chuàng)建互斥量時(shí)會(huì)先釋放一個(gè)互斥量,表示這個(gè)資源可以使用。任務(wù)想訪問資源時(shí),先獲取互斥量,等使用完資源后,再釋放它。也就是說互斥量一旦創(chuàng)建好后,要先獲取,后釋放,要在同一個(gè)任務(wù)中獲取和釋放。這也是互斥量和二進(jìn)制信號(hào)量的一個(gè)重要區(qū)別,二進(jìn)制信號(hào)量可以在隨便一個(gè)任務(wù)中獲取或釋放,然后也可以在任意一個(gè)任務(wù)中釋放或獲取。互斥量不同于二進(jìn)制信號(hào)量的還有:互斥量具有優(yōu)先級(jí)繼承機(jī)制,二進(jìn)制信號(hào)量沒有,互斥量不可以用于中斷服務(wù)程序,二進(jìn)制信號(hào)量可以。
初始化后的互斥量內(nèi)存如圖1-3所示。
圖1-3:初始化后的互斥量對(duì)象內(nèi)存
1.4創(chuàng)建遞歸互斥量
創(chuàng)建遞歸互斥量間接使用通用隊(duì)列創(chuàng)建函數(shù)xQueueGenericCreate()。創(chuàng)建遞歸互斥量API接口同樣是個(gè)宏,定義如下:
#definexSemaphoreCreateRecursiveMutex() \ xQueueCreateMutex(queueQUEUE_TYPE_RECURSIVE_MUTEX, NULL )
其中,宏queueQUEUE_TYPE_RECURSIVE_MUTEX用于通用隊(duì)列創(chuàng)建函數(shù),表示創(chuàng)建隊(duì)列的類型是遞歸互斥量,在文章FreeRTOS進(jìn)階之隊(duì)列示例分析關(guān)于通用隊(duì)列創(chuàng)建函數(shù)參數(shù)說明中提到了這個(gè)宏。
創(chuàng)建互斥量和創(chuàng)建遞歸互斥量是調(diào)用的同一個(gè)函數(shù)xQueueCreateMutex(),至于參數(shù)queueQUEUE_TYPE_RECURSIVE_MUTEX,我們?cè)贔reeRTOS一文中已經(jīng)知道,它只是用于可視化調(diào)試,因此創(chuàng)建互斥量和創(chuàng)建遞歸互斥量可以看作是一樣的,初始化后的遞歸互斥量對(duì)象內(nèi)存也和互斥量一樣,如圖1-3所示。
2.釋放信號(hào)量
無論二進(jìn)制信號(hào)量、計(jì)數(shù)信號(hào)量還是互斥量,它們都使用相同的獲取和釋放API函數(shù)。釋放信號(hào)量用于使信號(hào)量有效,分為不帶中斷保護(hù)和帶中斷保護(hù)兩個(gè)版本。
2.1 xSemaphoreGive()
用于釋放一個(gè)信號(hào)量,不帶中斷保護(hù)。被釋放的信號(hào)量可以是二進(jìn)制信號(hào)量、計(jì)數(shù)信號(hào)量和互斥量。注意遞歸互斥量并不能使用這個(gè)API函數(shù)釋放。其實(shí)信號(hào)量釋放是一個(gè)宏,真正調(diào)用的函數(shù)是xQueueGenericSend(),宏定義如下:
#definexSemaphoreGive( xSemaphore ) \ xQueueGenericSend( \ ( QueueHandle_t ) ( xSemaphore ), \ NULL, \ semGIVE_BLOCK_TIME, \ queueSEND_TO_BACK )
可以看出釋放信號(hào)量實(shí)際上是一次入隊(duì)操作,并且阻塞時(shí)間為0(由宏semGIVE_BLOCK_TIME定義)。
對(duì)于二進(jìn)制信號(hào)量和計(jì)數(shù)信號(hào)量,根據(jù)上一章的內(nèi)容可以總結(jié)出,釋放一個(gè)信號(hào)量的過程實(shí)際上可以簡化為兩種情況:第一,如果隊(duì)列未滿,隊(duì)列結(jié)構(gòu)體成員uxMessageWaiting加1,判斷是否有阻塞的任務(wù),有的話解除阻塞,然后返回成功信息(pdPASS);第二,如果隊(duì)列滿,返回錯(cuò)誤代碼(err_QUEUE_FULL),表示隊(duì)列滿。
對(duì)于互斥量要復(fù)雜些,因?yàn)榛コ饬烤哂袃?yōu)先級(jí)繼承機(jī)制。
優(yōu)先級(jí)繼承是個(gè)什么過程呢?我們舉個(gè)例子。某個(gè)資源X同時(shí)只能有一個(gè)任務(wù)訪問,現(xiàn)在有任務(wù)A和任務(wù)C都要訪問這個(gè)資源,任務(wù)A的優(yōu)先級(jí)為1,任務(wù)C的優(yōu)先級(jí)為10,所以任務(wù)C的優(yōu)先級(jí)大于任務(wù)A的優(yōu)先級(jí)。我們用互斥量保護(hù)資源X,并且當(dāng)前任務(wù)A正在訪問資源X。在任務(wù)A訪問資源X的過程中,來了一個(gè)中斷,中斷事件使得任務(wù)C執(zhí)行。任務(wù)C執(zhí)行的過程中,也想訪問資源X,但是因?yàn)橘Y源X還被任務(wù)A獨(dú)占著,所以任務(wù)C無法獲取互斥量,會(huì)進(jìn)入阻塞狀態(tài)。此時(shí),低優(yōu)先級(jí)任務(wù)A會(huì)繼承高優(yōu)先級(jí)任務(wù)C的優(yōu)先級(jí),任務(wù)A的優(yōu)先級(jí)臨時(shí)的被提升,優(yōu)先級(jí)變成10。這個(gè)機(jī)制能夠?qū)⒁呀?jīng)發(fā)生的優(yōu)先級(jí)反轉(zhuǎn)影響降低到最小。
那么什么是優(yōu)先級(jí)反轉(zhuǎn)呢?還是看上面的例子,任務(wù)C的優(yōu)先級(jí)高于任務(wù)A,但是任務(wù)C因?yàn)闆]有獲得互斥量而進(jìn)入阻塞,只能等待低優(yōu)先級(jí)的任務(wù)A釋放互斥量后才能運(yùn)行,這種情況就是優(yōu)先級(jí)反轉(zhuǎn)。
那為什么優(yōu)先級(jí)繼承可以降低優(yōu)先級(jí)反轉(zhuǎn)的影響呢?還是看上面的例子,不過我們?cè)僭黾右粋€(gè)優(yōu)先級(jí)為5的任務(wù)B,這三個(gè)任務(wù)都處于就緒狀態(tài)。如果沒有優(yōu)先級(jí)繼承機(jī)制,三個(gè)任務(wù)的優(yōu)先級(jí)順序?yàn)槿蝿?wù)C>任務(wù)B>任務(wù)A。當(dāng)任務(wù)C因?yàn)榈貌坏交コ饬慷枞?,任?wù)B會(huì)獲取CPU權(quán)限,等到任務(wù)B主動(dòng)或被動(dòng)讓出CPU后,任務(wù)A才會(huì)執(zhí)行,任務(wù)A釋放互斥量后,任務(wù)C才能得到運(yùn)行。再看一下有優(yōu)先級(jí)繼承的情況,當(dāng)任務(wù)C因?yàn)榈貌坏交コ饬慷枞?,任?wù)A繼承任務(wù)C的優(yōu)先級(jí),現(xiàn)在三個(gè)任務(wù)的優(yōu)先級(jí)順序?yàn)槿蝿?wù)C=任務(wù)A>任務(wù)B。當(dāng)任務(wù)C因?yàn)榈貌坏交コ饬慷枞?,任?wù)A會(huì)獲得CPU權(quán)限,等到任務(wù)A釋放互斥量后,任務(wù)C就會(huì)得到運(yùn)行???,任務(wù)C等待的時(shí)間變短了。
有了上面的基礎(chǔ)理論,我們就很好理解為什么釋放互斥量會(huì)比較復(fù)雜了。還是可以簡化為兩種情況:第一,如果隊(duì)列未滿,除了隊(duì)列結(jié)構(gòu)體成員uxMessageWaiting加1外,還要判斷獲取互斥量的任務(wù)是否有優(yōu)先級(jí)繼承,如果有的話,還要將任務(wù)的優(yōu)先級(jí)恢復(fù)到原始值。當(dāng)然,恢復(fù)到原來值也是有條件的,就是該任務(wù)必須在沒有使用其它互斥量的情況下,才能將繼承的優(yōu)先級(jí)恢復(fù)到原始值。然后判斷是否有阻塞的任務(wù),有的話解除阻塞,最后返回成功信息(pdPASS);第二,如果如果隊(duì)列滿,返回錯(cuò)誤代碼(err_QUEUE_FULL),表示隊(duì)列滿。
2.2xSemaphoreGiveFromISR()
用于釋放一個(gè)信號(hào)量,帶中斷保護(hù)。被釋放的信號(hào)量可以是二進(jìn)制信號(hào)量和計(jì)數(shù)信號(hào)量。和普通版本的釋放信號(hào)量API函數(shù)不同,它不能釋放互斥量,這是因?yàn)榛コ饬坎豢梢栽谥袛嘀惺褂?!互斥量的?yōu)先級(jí)繼承機(jī)制只能在任務(wù)中起作用,在中斷中毫無意義。帶中斷保護(hù)的信號(hào)量釋放其實(shí)也是一個(gè)宏,真正調(diào)用的函數(shù)是xQueueGiveFromISR (),宏定義如下:
#definexSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken ) \ xQueueGiveFromISR( \ ( QueueHandle_t ) ( xSemaphore), \ ( pxHigherPriorityTaskWoken ) )
我們看真正被調(diào)用的函數(shù)源碼(經(jīng)過整理后的):
BaseType_t xQueueGiveFromISR( QueueHandle_t xQueue, BaseType_t * constpxHigherPriorityTaskWoken ) { BaseType_t xReturn; UBaseType_t uxSavedInterruptStatus; Queue_t * const pxQueue = ( Queue_t * ) xQueue; uxSavedInterruptStatus =portSET_INTERRUPT_MASK_FROM_ISR(); { /*當(dāng)隊(duì)列用于實(shí)現(xiàn)信號(hào)量時(shí),永遠(yuǎn)不會(huì)有數(shù)據(jù)出入隊(duì)列,但是任然要檢查隊(duì)列是否為空 */ if( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) { /* 一個(gè)任務(wù)可以獲取多個(gè)互斥量,但是只能有一個(gè)繼承優(yōu)先級(jí),如果任務(wù)是互斥量的持有者,則互斥量不允許在中斷服務(wù)程序中釋放.因此這里不需要判斷是否要恢復(fù)任務(wù)的原始優(yōu)先級(jí)值,只是簡單更新隊(duì)列項(xiàng)計(jì)數(shù)器. */ ++( pxQueue->uxMessagesWaiting ); /* 如果列表上鎖,不能改變隊(duì)列的事件列表. */ if( pxQueue->xTxLock == queueUNLOCKED ) { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive) ) == pdFALSE ) { if(xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive) ) != pdFALSE ) { /* 解除阻塞的任務(wù)有更高優(yōu)先級(jí),因此記錄上下文切換請(qǐng)求*/ if(pxHigherPriorityTaskWoken != NULL ) { *pxHigherPriorityTaskWoken= pdTRUE; } } } } else { /* Increment thelock count so the task that unlocks the queue knows that data wasposted while it was locked. */ ++( pxQueue->xTxLock ); } xReturn = pdPASS; } else { xReturn = errQUEUE_FULL; } } portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus ); return xReturn; }
因?yàn)椴簧婕盎コ饬?,不涉及阻塞,函?shù)xQueueGiveFromISR()異常簡單,如果隊(duì)列滿,直接返回錯(cuò)誤代碼(err_QUEUE_FULL);如果隊(duì)列未滿,則將隊(duì)列結(jié)構(gòu)體成員uxMessageWaiting加1,然后視隊(duì)列是否上鎖而決定是否解除任務(wù)阻塞(如果有得話)。如果你覺得難以理解,則需要先看看FreeRTOS進(jìn)階之隊(duì)列示例分析。
3.獲取信號(hào)量
無論二進(jìn)制信號(hào)量、計(jì)數(shù)信號(hào)量還是互斥量,它們都使用相同的獲取和釋放API函數(shù)。釋獲取信號(hào)量會(huì)消耗信號(hào)量,如果獲取信號(hào)量失敗,任務(wù)可能會(huì)阻塞,阻塞時(shí)間由函數(shù)參數(shù)xBlockTime指定,如果為0,則直接返回,不阻塞。獲取信號(hào)量分為不帶中斷保護(hù)和帶中斷保護(hù)兩個(gè)版本。
3.1 xSemaphoreTake
用于獲取信號(hào)量,不帶中斷保護(hù)。獲取的信號(hào)量可以是二進(jìn)制信號(hào)量、計(jì)數(shù)信號(hào)量和互斥量。注意遞歸互斥量并不能使用這個(gè)API函數(shù)獲取。其實(shí)獲取信號(hào)量是一個(gè)宏,真正調(diào)用的函數(shù)是xQueueGenericReceive (),宏定義如下:
#definexSemaphoreTake( xSemaphore, xBlockTime ) \ xQueueGenericReceive( \ ( QueueHandle_t ) ( xSemaphore ), \ NULL, \ ( xBlockTime ), \ pdFALSE )
通過上面的宏定義可以看出,獲取信號(hào)量實(shí)際上是執(zhí)行出隊(duì)操作。
對(duì)于二進(jìn)制信號(hào)量和計(jì)數(shù)信號(hào)量,可以簡化為三種情況:
第一,如果隊(duì)列不為空,隊(duì)列結(jié)構(gòu)體成員uxMessageWaiting減1,判斷是否有因入隊(duì)而阻塞的任務(wù),有的話解除阻塞,然后返回成功信息(pdPASS);
第二,如果隊(duì)列為空并且阻塞時(shí)間為0,則直接返回錯(cuò)誤碼(errQUEUE_EMPTY),表示隊(duì)列為空;
第三,如果隊(duì)列為空并且阻塞時(shí)間不為0,則任務(wù)會(huì)因?yàn)榈却盘?hào)量而進(jìn)入阻塞狀態(tài),任務(wù)會(huì)被掛接到延時(shí)列表中。
對(duì)于互斥量,也可以簡化為三種情況,但是過程要復(fù)雜一些:
第一,如果隊(duì)列不為空,隊(duì)列結(jié)構(gòu)體成員uxMessageWaiting減1、將當(dāng)前任務(wù)TCB結(jié)構(gòu)體成員uxMutexesHeld加1,表示任務(wù)獲取互斥量的個(gè)數(shù)、將隊(duì)列結(jié)構(gòu)體成員指針pxMutexHolder指向任務(wù)TCB、判斷是否有因入隊(duì)而阻塞的任務(wù),有的話解除阻塞,然后返回成功信息(pdPASS);
第二,如果隊(duì)列為空并且阻塞時(shí)間為0,則直接返回錯(cuò)誤碼(errQUEUE_EMPTY),表示隊(duì)列為空;
第三,如果隊(duì)列為空并且阻塞時(shí)間不為0,則任務(wù)會(huì)因?yàn)榈却盘?hào)量而進(jìn)入阻塞狀態(tài),在將任務(wù)掛接到延時(shí)列表之前,會(huì)判斷當(dāng)前任務(wù)和擁有互斥量的任務(wù)優(yōu)先級(jí)哪個(gè)高,如果當(dāng)前任務(wù)優(yōu)先級(jí)高,則擁有互斥量的任務(wù)繼承當(dāng)前任務(wù)優(yōu)先級(jí)。
3.2xSemaphoreTakeFromISR()
用于獲取信號(hào)量,帶中斷保護(hù)。獲取的信號(hào)量可以是二進(jìn)制信號(hào)量和計(jì)數(shù)信號(hào)量。和普通版本的獲取信號(hào)量API函數(shù)不同,它不能獲取互斥量,這是因?yàn)榛コ饬坎豢梢栽谥袛嘀惺褂?!互斥量的?yōu)先級(jí)繼承機(jī)制只能在任務(wù)中起作用,在中斷中毫無意義。帶中斷保護(hù)的獲取信號(hào)量其實(shí)也是一個(gè)宏,真正調(diào)用的函數(shù)是xQueueReceiveFromISR (),宏定義如下:
#definexSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken ) \ xQueueReceiveFromISR( \ ( QueueHandle_t ) ( xSemaphore ), \ NULL, \ ( pxHigherPriorityTaskWoken ) )
同樣因?yàn)椴簧婕盎コ饬?,不涉及阻塞,函?shù)xQueueReceiveFromISR ()同樣異常簡單:如果隊(duì)列為空,直接返回錯(cuò)誤代碼(pdFAIL);如果隊(duì)列非空,則將隊(duì)列結(jié)構(gòu)體成員uxMessageWaiting減1,然后視隊(duì)列是否上鎖而決定是否解除任務(wù)阻塞(如果有得話)。
4.釋放遞歸互斥量
函數(shù)xSemaphoreGiveRecursive()用于釋放一個(gè)遞歸互斥量。已經(jīng)獲取遞歸互斥量的任務(wù)可以重復(fù)獲取該遞歸互斥量。使用xSemaphoreTakeRecursive()函數(shù)成功獲取幾次遞歸互斥量,就要使用xSemaphoreGiveRecursive()函數(shù)返還幾次,在此之前遞歸互斥量都處于無效狀態(tài)。比如,某個(gè)任務(wù)成功獲取5次遞歸互斥量,那么在它沒有返還5次該遞歸互斥量之前,這個(gè)互斥量對(duì)別的任務(wù)無效。
像其它信號(hào)量一樣,xSemaphoreGiveRecursive()也是一個(gè)宏定義,它最終使用現(xiàn)有的隊(duì)列機(jī)制,實(shí)際執(zhí)行的函數(shù)是xQueueGiveMutexRecursive(),這個(gè)宏定義如下所示:
#definexSemaphoreGiveRecursive( xMutex ) \ xQueueGiveMutexRecursive( (xMutex ) )
我們重點(diǎn)來看函數(shù)xQueueGiveMutexRecursive()的實(shí)現(xiàn)過程。經(jīng)過整理后(去除跟蹤調(diào)試語句)的源碼如下所示:
#if ( configUSE_RECURSIVE_MUTEXES == 1 ) BaseType_txQueueGiveMutexRecursive( QueueHandle_t xMutex ) { BaseType_t xReturn; Queue_t * const pxMutex = ( Queue_t * ) xMutex; /* 互斥量和遞歸互斥量要在同一個(gè)任務(wù)中獲取和釋放,當(dāng)獲取互斥量或遞歸互斥量時(shí),隊(duì)列結(jié)構(gòu)體成員指針pxMutexHolder指向獲取互斥量或遞歸互斥量的任務(wù)TCB,所以在釋放遞歸互斥量時(shí)需要檢查這個(gè)指針指向的TCB是否是和當(dāng)前任務(wù)TCB相同,如果不相同是不能釋放這個(gè)遞歸互斥量的! 注:釋放互斥量時(shí),這個(gè)檢查不是必須的,FreeRTOS的作者將這個(gè)檢查放在了斷言中(configASSERT( pxTCB == pxCurrentTCB);).*/ if( pxMutex->pxMutexHolder == ( void * )xTaskGetCurrentTaskHandle() ) { /* 每當(dāng)任務(wù)獲取遞歸互斥量時(shí),隊(duì)列結(jié)構(gòu)體成員u.uxRecursiveCallCount會(huì)加1,互斥量不會(huì)使用這個(gè)變量,它用來保存遞歸次數(shù).所以,在釋放遞歸互斥量的時(shí)候要將它減1*/ ( pxMutex->u.uxRecursiveCallCount)--; /* 遞歸計(jì)數(shù)器為0? */ if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 ) { /* 調(diào)用入隊(duì)函數(shù)釋放一個(gè)互斥量,注意阻塞時(shí)間(由宏queueMUTEX_GIVE_BLOCK_TIME定義)為0 */ ( void ) xQueueGenericSend( pxMutex, NULL,queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK ); } xReturn = pdPASS; } else { /* 如果不是本任務(wù)擁有這個(gè)互斥量,則直接返回錯(cuò)誤碼 */ xReturn = pdFAIL; } return xReturn; } #endif /* configUSE_RECURSIVE_MUTEXES */
這個(gè)函數(shù)是帶條件編譯的,只有將宏configUSE_RECURSIVE_MUTEXES定義為1才會(huì)編譯這個(gè)函數(shù)。
互斥量和遞歸互斥量的最大區(qū)別在于一個(gè)遞歸互斥量可以被已經(jīng)獲取這個(gè)遞歸互斥量的任務(wù)重復(fù)獲取,這個(gè)遞歸調(diào)用功能是通過隊(duì)列結(jié)構(gòu)體成員u.uxRecursiveCallCount實(shí)現(xiàn)的。這個(gè)變量用于存儲(chǔ)遞歸調(diào)用的次數(shù),每次獲取遞歸互斥量后,這個(gè)變量加1,在釋放遞歸互斥量后,這個(gè)變量減1。只有這個(gè)變量減到0,即釋放和獲取的次數(shù)相等時(shí),互斥量才能再次有效,使用入隊(duì)函數(shù)釋放一個(gè)遞歸互斥量。
5.獲取遞歸互斥量
函數(shù)xSemaphoreTakeRecursive()用于獲取一個(gè)遞歸互斥量。像其它信號(hào)量一樣,xSemaphoreTakeRecursive()也是一個(gè)宏定義,它最終使用現(xiàn)有的隊(duì)列機(jī)制,實(shí)際執(zhí)行的函數(shù)是xQueueTakeMutexRecursive(),這個(gè)宏定義如下所示:
#definexSemaphoreTakeRecursive( xMutex, xBlockTime ) \ xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
獲取遞歸互斥量具有阻塞超時(shí)參數(shù),如果互斥量正被別的任務(wù)使用,可以阻塞設(shè)定的時(shí)間。我們重點(diǎn)來看函數(shù)xQueueTakeMutexRecursive()的實(shí)現(xiàn)過程。經(jīng)過整理后(去除跟蹤調(diào)試語句)的源碼如下所示:
#if ( configUSE_RECURSIVE_MUTEXES == 1 ) BaseType_txQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_txTicksToWait ) { BaseType_t xReturn; Queue_t * const pxMutex = ( Queue_t * ) xMutex; /*互斥量和遞歸互斥量要在同一個(gè)任務(wù)中獲取和釋放,遞歸互斥量可以在一個(gè)任務(wù)中多次獲取,當(dāng)?shù)谝淮潍@取遞歸互斥量時(shí),隊(duì)列結(jié)構(gòu)體成員指針pxMutexHolder指向獲取遞歸互斥量的任務(wù)TCB,在此獲取這個(gè)遞歸互斥量時(shí),如果這個(gè)指針指向的TCB和當(dāng)前任務(wù)TCB相同,只需要將遞歸次數(shù)計(jì)數(shù)器u.uxRecursiveCallCount加1即可,不需要再操作隊(duì)列.*/ if( pxMutex->pxMutexHolder == ( void * )xTaskGetCurrentTaskHandle() ) { ( pxMutex->u.uxRecursiveCallCount)++; xReturn = pdPASS; } else { /*調(diào)用出隊(duì)函數(shù)*/ xReturn =xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE ); /* 成功獲取遞歸互斥量后,要將遞歸次數(shù)計(jì)數(shù)器加1*/ if( xReturn != pdFAIL ) { ( pxMutex->u.uxRecursiveCallCount)++; } } return xReturn; } #endif /* configUSE_RECURSIVE_MUTEXES */
這個(gè)函數(shù)是帶條件編譯的,只有將宏configUSE_RECURSIVE_MUTEXES定義為1才會(huì)編譯這個(gè)函數(shù)。
程序邏輯比較簡單,如果是第一次獲取這個(gè)遞歸互斥量,直接使用出隊(duì)函數(shù),成功后將遞歸次數(shù)計(jì)數(shù)器加1;如果是第二次或者更多次獲取這個(gè)遞歸互斥量,則只需要將遞歸次數(shù)計(jì)數(shù)器加1即可。
以上就是FreeRTOS進(jìn)階信號(hào)量示例的完全解析的詳細(xì)內(nèi)容,更多關(guān)于FreeRTOS進(jìn)階信號(hào)量分析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
FreeRTOS編碼標(biāo)準(zhǔn)及風(fēng)格指南
這篇文章主要為大家介紹了FreeRTOS編碼標(biāo)準(zhǔn)及風(fēng)格指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)在Cortex-M3上的移植過程
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)在Cortex-M3上的移植過程的示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS進(jìn)階之任務(wù)創(chuàng)建完全解析
這篇文章主要為大家介紹了FreeRTOS進(jìn)階之任務(wù)創(chuàng)建完全解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)的任務(wù)應(yīng)用函數(shù)詳解
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)的任務(wù)應(yīng)用函數(shù)的解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04freertos實(shí)時(shí)操作系統(tǒng)臨界段保護(hù)開關(guān)中斷及進(jìn)入退出
這篇文章主要介紹了freertos實(shí)時(shí)操作系統(tǒng)臨界段保護(hù)開關(guān)中斷及進(jìn)入退出,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS進(jìn)階內(nèi)存管理示例完全解析
這篇文章主要為大家介紹了FreeRTOS進(jìn)階內(nèi)存管理示例的完全解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)隊(duì)列基礎(chǔ)
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)隊(duì)列基礎(chǔ),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS信號(hào)量API函數(shù)基礎(chǔ)教程
這篇文章主要為大家介紹了FreeRTOS信號(hào)量API函數(shù)的基礎(chǔ)教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)隊(duì)列的API函數(shù)講解
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)隊(duì)列的API函數(shù)講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04