開始學習FreeRTOS,學習參考書籍和視頻來自正點原子FreeRTOS源碼詳解與應用開發,北京航空航天大學出版社
FreeRTOS信號量
——————(正點原子FreeRTOS學習筆記)
1 二值信號量
1.1 二值信號量簡介
二值信號量通常用於互斥訪問或同步,二值信號量和互斥信號量非常類似,但是還是有一些細微的差別,互斥信號量擁有優先級繼承機制,二值信號量沒有優先級繼承。因此二值信號另更適合用於同步(任務與任務或任務與中斷的同步),而互斥信號量適合用於簡單的互斥訪問。
和隊列一樣,信號量 API 函數允許設置一個阻塞時間,阻塞時間是當任務獲取信號量的時候由於信號量無效從而導致任務進入阻塞態的最大時鐘節拍數。如果多個任務同時阻塞在同一個信號量上的話那麼優先級最高的哪個任務優先獲得信號量,這樣當信號量有效的時候高優先級的任務就會解除阻塞狀態。
二值信號量其實就是一個只有一個隊列項的隊列,這個特殊的隊列要麼是滿的,要麼是空的,這不正好就是二值的嗎? 任務和中斷使用這個特殊隊列不用在乎隊列中存的是什麼消息,只需要知道這個隊列是滿的還是空的。可以利用這個機制來完成任務與中斷之間的同步。
使用二值信號量來完成中斷與任務同步的這個機制中,任務優先級確保了外設能夠得到及時的處理,這樣做相當於推遲了中斷處理過程。也可以使用隊列來替代二值信號量,在外設事件的中斷服務函數中獲取相關數據,並將相關的數據通過隊列發送給任務。如果隊列無效的話任務就進入阻塞態,直至隊列中有數據,任務接收到數據以後就開始相關的處理過程。下面幾個步驟演示了二值信號量的工作過程。
1 、二值信號量無效
在圖 14.2.1.1 中任務 Task 通過函數 xSemaphoreTake()獲取信號量,但是此時二值信號量無
效,所以任務 Task 進入阻塞態。
2 、中斷 釋放 信號量
此時中斷髮生了,在中斷服務函數中通過函數 xSemaphoreGiveFromISR()釋放信號量,因此信號量變爲有效。
3、任務獲取信號量成功
由於信號量已經有效了,所以任務 Task 獲取信號量成功,任務從阻塞態解除,開始執行相關的處理過程
4、任務再次進入阻塞態
由於任務函數一般都是一個大循環,所以在任務做完相關的處理以後就會再次調用函數xSemaphoreTake()獲取信號量。在執行完第三步以後二值信號量就已經變爲無效的了,所以任務將再次進入阻塞態,和第一步一樣,直至中斷再次發生並且調用函數 xSemaphoreGiveFromISR()釋放信號量。
1.2 創建二值信號量
同隊列一樣,要想使用二值信號量就必須先創建二值信號量,二值信號量創建函數 如表 14.2.2所示:
1 、函數 vSemaphoreCreateBinary ()
此函數是老版本 FreeRTOS 中的創建二值信號量函數,新版本已經不再使用了,新版本的FreeRTOS 使用 xSemaphoreCreateBinary()來替代此函數,這裏還保留這個函數是爲了兼容那些基於老版本 FreeRTOS 而做的應用層代碼。此函數是個宏,具體創建過程是由函數xQueueGenericCreate()來完成的,在文件
semphr.h 中有如下定義:
void vSemaphoreCreateBinary( SemaphoreHandle_t xSemaphore )
參數:
xSemaphore:保存創建成功的二值信號量句柄。
返回值:
NULL: 二值信號量創建失敗。
其他值: 二值信號量創建成功。
2 、函數 xSemaphoreCreateBinary()
此函數是 vSemaphoreCreateBinary()的新版本,新版本的 FreeRTOS 中統一用此函數來創建二值信號量。使用此函數創建二值信號量的話信號量所需要的 RAM 是由 FreeRTOS 的內存管理部分來動態分配的。此函數創建好的二值信號量默認是空的,也就是說剛創建好的二值信號量使用函數
xSemaphoreTake()是獲取不到的,此函數也是個宏,具體創建過程是由函數xQueueGenericCreate()來完成的,函數原型如下:
SemaphoreHandle_t xSemaphoreCreateBinary( void )
參數:
無。
返回值:
NULL: 二值信號量創建失敗。
其他值: 創建成功的二值信號量的句柄。
3 、函數 xSemaphoreCreateBinaryStatic()
此函數也是創建二值信號量的,只不過使用此函數創建二值信號量的話信號量所需要的RAM 需要由用戶來分配,此函數是個宏,具體創建過程是通過函數 xQueueGenericCreateStatic()來完成的,函數原型如下:
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )
參數:
pxSemaphoreBuffer: :此參數指向一個 StaticSemaphore_t 類型的變量,用來保存信號量結構體。
返回值:
NULL: 二值信號量創建失敗。
其他值: 創建成功的二值信號量句柄。
1.3 二值信號量創建過程分析
上一小節講了三個用於二值信號量創建的函數,兩個動態的創建函數和一個靜態的創建函數。本節就來分析一下這兩個動態的創建函數,靜態創建函數和動態的類似,就不做分析了。首先來看一下老版本的二值信號量動態創建函數 vSemaphoreCreateBinary(),函數代碼如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define vSemaphoreCreateBinary( xSemaphore ) \
{ \
( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, \ (1)
semSEMAPHORE_QUEUE_ITEM_LENGTH, \
queueQUEUE_TYPE_BINARY_SEMAPHORE ); \
if( ( xSemaphore ) != NULL ) \
{ \
( void ) xSemaphoreGive( ( xSemaphore ) ); \ (2)
} \
}
#endif
(1)、上面說了二值信號量是在隊列的基礎上實現的,所以創建二值信號量就是創建隊列的過程。這裏使用函數 xQueueGenericCreate()創建了一個隊列,隊列長度爲 1,隊列項長度爲 0,隊列類型爲 queueQUEUE_TYPE_BINARY_SEMAPHORE,也就是二值信號量。
(2)、當二值信號量創建成功以後立即調用函數 xSemaphoreGive()釋放二值信號量,此時新
創建的二值信號量有效。在來看一下新版本的二值信號量創建函數 xSemaphoreCreateBinary(),函數代碼如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() \
xQueueGenericCreate( ( UBaseType_t ) 1, \
semSEMAPHORE_QUEUE_ITEM_LENGTH, \
queueQUEUE_TYPE_BINARY_SEMAPHORE ) \
#endif
可以看出新版本的二值信號量創建函數也是使用函數 xQueueGenericCreate()來創建一個類型爲queueQUEUE_TYPE_BINARY_SEMAPHORE、長度爲 1、隊列項長度爲 0 的隊列。這一步和老版本的二值信號量創建函數一樣,唯一不同的就是新版本的函數在成功創建二值信號量以後不會立即調用函數
xSemaphoreGive()釋放二值信號量。也就是說新版函數創建的二值信號量默認是無效的,而老版本是有效的。
大家注意看,創建的隊列是個沒有存儲區的隊列,前面說了使用隊列是否爲空來表示二值信號量,而隊列是否爲空可以通過隊列結構體的成員變量 uxMessagesWaiting 來判斷。
1.4 釋放信號量
釋放信號量的函數有兩個,如表 14.2.4.1 所示:
同隊列一樣,釋放信號量也分爲任務級和中斷級。還有!不管是二值信號量、計數型信號量還是互斥信號量,它們都使用表 14.2.4.1 中的函數釋放信號量,遞歸互斥信號量有專用的釋放函數。
1、數 函數 xSemaphoreGive()
此函數用於釋放二值信號量、計數型信號量或互斥信號量,此函數是一個宏,真正釋放信號量的過程是由函數 xQueueGenericSend()來完成的,函數原型如下:
BaseType_t xSemaphoreGive( xSemaphore )
參數:
xSemaphore :要釋放的信號量句柄。
返回值:
pdPASS: 釋放信號量成功。
errQUEUE_FULL: 釋放信號量失敗。
我們再來看一下函數 xSemaphoreGive()的具體內容,此函數在文件 semphr.h 中有如下定義:
#define xSemaphoreGive( xSemaphore ) \
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), \
NULL, \
semGIVE_BLOCK_TIME, \
queueSEND_TO_BACK ) \
可以看出任務級釋放信號量就是向隊列發送消息的過程,只是這裏並沒有發送具體的消息,阻塞時間爲 0(宏 semGIVE_BLOCK_TIME 爲 0),入隊方式採用的後向入隊。具體入隊過程第十三章已經做了詳細的講解,入隊的時候隊列結構體成員變量 uxMessagesWaiting 會加一,對於二值信號量通過判斷
uxMessagesWaiting 就可以知道信號量是否有效了,當 uxMessagesWaiting 爲1 的話說明二值信號量有效,爲 0 就無效。如果隊列滿的話就返回錯誤值 errQUEUE_FULL,提示隊列滿,入隊失敗。
2 、函數 xSemaphoreGiveFromISR()
此函數用於在中斷中釋放信號量,此函數只能用來釋放二值信號量和計數型信號量,絕對不能用來在中斷服務函數中釋放互斥信號量!此函數是一個宏,真正執行的是函數
xQueueGiveFromISR(),此函數原型如下:
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
參數:
xSemaphore: 要釋放的信號量句柄。
pxHigherPriorityTaskWoken: 標記退出此函數以後是否進行任務切換,這個變量的值由這
三個函數來設置的,用戶不用進行設置,用戶只需要提供一個變量來保存這個值就行了。當此值爲 pdTRUE 的時候在退出中斷服務函數之前一定要進行一次任務切換。
返回值:
pdPASS: 釋放信號量成功。
errQUEUE_FULL: 釋放信號量失敗。
在中斷中釋放信號量真正使用的是函數 xQueueGiveFromISR(),此函數和中斷級通用入隊函數 xQueueGenericSendFromISR()極其類似!只是針對信號量做了微小的改動。函數xSemaphoreGiveFromISR()不能用於在中斷中釋放互斥信號量,因爲互斥信號量涉及到優先級繼承的問題,而中斷不屬於任務,沒法處理中斷優先級繼承。大家可以參考第十三章分析函數
xQueueGenericSendFromISR()的過程來分析 xQueueGiveFromISR()。
1.5 獲取信號量
獲取信號量也有兩個函數,如表 14.2.5.1 所示:
同釋放信號量的 API 函數一樣,不管是二值信號量、計數型信號量還是互斥信號量,它們都使用表 14.2.5.1 中的函數獲取信號量。
1 、函數 xSemaphoreTake()
此函數用於獲取二值信號量、計數型信號量或互斥信號量,此函數是一個宏,真正獲取信號量的過程是由函數 xQueueGenericReceive ()來完成的,函數原型如下:
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,
TickType_t xBlockTime)
參數:
xSemaphore :要獲取的信號量句柄。
xBlockTime: 阻塞時間。
返回值:
pdTRUE: 獲取信號量成功。
pdFALSE: 超時,獲取信號量失敗。
再來看一下函數 xSemaphoreTake ()的具體內容,此函數在文件 semphr.h 中有如下定義:
#define xSemaphoreTake( xSemaphore, xBlockTime ) \
xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), \
NULL, \
( xBlockTime ), \
pdFALSE ) \
獲取信號量的過程其實就是讀取隊列的過程,只是這裏並不是爲了讀取隊列中的消息。在第十三章講解函數 xQueueGenericReceive()的時候說過如果隊列爲空並且阻塞時間爲 0 的話就立即返回 errQUEUE_EMPTY,表示隊列滿。如果隊列爲空並且阻塞時間不爲 0 的話就將任務添加到延時列表中。如果隊列不爲空的話就從隊列中讀取數據(獲取信號量不執行這一步),數據讀取完成以後還需要將隊列結構體成員變量
uxMessagesWaiting 減一,然後解除某些因爲入隊而阻塞的任務,最後返回 pdPASS 表示出對成功。互斥信號量涉及到優先級繼承,處理方式不同,後面講解互斥信號量的時候在詳細的講解。
2 、函數 xSemaphoreTakeFromISR ()
此函數用於在中斷服務函數中獲取信號量,此函數用於獲取二值信號量和計數型信號量,絕 對 不 能 使 用 此 函 數 來 獲 取 互 斥 信 號 量 ! 此 函 數 是 一 個 宏 , 真 正 執 行 的 是 函 數
xQueueReceiveFromISR (),此函數原型如下:
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
參數:
xSemaphore: 要獲取的信號量句柄。
pxHigherPriorityTaskWoken: 標記退出此函數以後是否進行任務切換,這個變量的值由這
三個函數來設置的,用戶不用進行設置,用戶只需要提供一個變量來保存這個值就行了。當此值爲 pdTRUE 的時候在退
出中斷服務函數之前一定要進行一次任務切換。
返回值:
pdPASS: 獲取信號量成功。
pdFALSE: 獲取信號量失敗。
在中斷中獲取信號量真正使用的是函數 xQueueReceiveFromISR (),這個函數就是中斷級出隊函數!當隊列不爲空的時候就拷貝隊列中的數據(用於信號量的時候不需要這一步),然後將隊列結構體中的成員變量 uxMessagesWaiting 減一,如果有任務因爲入隊而阻塞的話就解除阻塞態,當解除阻塞的任務擁有更高優先級的話就將參數pxHigherPriorityTaskWoken
設置爲pdTRUE,最後返回 pdPASS 表示出隊成功。如果隊列爲空的話就直接返回 pdFAIL 表示出隊失敗!這個函數還是很簡單的。
2 計數型信號量
2.1 計數型信號量簡介
有些資料中也將計數型信號量叫做數值信號量,二值信號量相當於長度爲 1 的隊列,那麼計數型信號量就是長度大於 1 的隊列。同二值信號量一樣,用戶不需要關心隊列中存儲了什麼數據,只需要關心隊列是否爲空即可。計數型信號量通常 用於如下兩個場合:
1 、事件計數
在這個場合中,每次事件發生的時候就在事件處理函數中釋放信號量(增加信號量的計數值),其他任務會獲取信號量(信號量計數值減一,信號量值就是隊列結構體成員變量uxMessagesWaiting)來處理事件。在這種場合中創建的計數型信號量初始計數值爲 0。
2 、資源管理
在這個場合中,信號量值代表當前資源的可用數量,比如停車場當前剩餘的停車位數量。一個任務要想獲得資源的使用權,首先必須獲取信號量,信號量獲取成功以後信號量值就會減一。當信號量值爲 0 的時候說明沒有資源了。當一個任務使用完資源以後一定要釋放信號量,釋放信號量以後信號量值會加一。在這個場合中創建的計數型信號量初始值應該是資源的數量,比如停車場一共有
100 個停車位,那麼創建信號量的時候信號量值就應該初始化爲 100。
2.2 創建計數型信號量
FreeRTOS 提供了兩個計數型信號量創建函數,如表 14.4.2.1 所示:
1 、函數 xSemaphoreCreateCounting()
此函數用於創建一個計數型信號量,所需要的內存通過動態內存管理方法分配。此函數本質是一個宏,真正完成信號量創建的是函數 xQueueCreateCountingSemaphore(),此函數原型如下:
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount )
參數:
uxMaxCount: 計數信號量最大計數值,當信號量值等於此值的時候釋放信號量就會失敗。
uxInitialCount: 計數信號量初始值。
返回值:
NULL: 計數型信號量創建失敗。
其他值: 計數型信號量創建成功,返回計數型信號量句柄。
2 、函數 xSemaphoreCreateCountingStatic()
此函數也是用來創建計數型信號量的,使用此函數創建計數型信號量的時候所需要的內存需要由用戶分配。此函數也是一個宏,真正執行的是函數xQueueCreateCountingSemaphoreStatic(),函數原型如下:
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t * pxSemaphoreBuffer )
參數:
uxMaxCount: 計數信號量最大計數值,當信號量值等於此值的時候釋放信號量就會失敗。
uxInitialCount: 計數信號量初始值。
pxSemaphoreBuffer :指向一個 StaticSemaphore_t 類型的變量,用來保存信號量結構體。
返回值:
NULL: 計數型信號量創建失敗。
其他值: 計數型號量創建成功,返回計數型信號量句柄。
2.3 計數型信號量創建過程分析
這裏只分析動態創建計數型信號量函數 xSemaphoreCreateCounting(),此函數是個宏,定義如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) \
xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) ) \
#endif
可以看出,真正幹事的是函數 xQueueCreateCountingSemaphore(),此函數在文件 queue.c 中
有如下定義:
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount,
const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle;
configASSERT( uxMaxCount != 0 );
configASSERT( uxInitialCount <= uxMaxCount );
xHandle = xQueueGenericCreate( uxMaxCount,\ (1)
queueSEMAPHORE_QUEUE_ITEM_LENGTH, \
queueQUEUE_TYPE_COUNTING_SEMAPHORE );
if( xHandle != NULL )
{
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount; (2)
traceCREATE_COUNTING_SEMAPHORE();
}
else
{
traceCREATE_COUNTING_SEMAPHORE_FAILED();
}
return xHandle;
}
(1)、計數型信號量也是在隊列的基礎上實現的,所以需要調用函數 xQueueGenericCreate()創 建 一 個 隊 列 , 隊 列 長 度 爲 uxMaxCount , 對 列 項 長 度 爲queueSEMAPHORE_QUEUE_ITEM_LENGTH( 此 宏 爲 0) , 隊 列 的 類 型 爲queueQUEUE_TYPE_COUNTING_SEMAPHORE,表示是個計數型信號量。
(2)、隊列結構體的成員變量 uxMessagesWaiting 用於計數型信號量的計數,根據計數型信號量的初始值來設置 uxMessagesWaiting。
3 互斥信號量
3.1 互斥信號量簡介
互斥信號量其實就是一個擁有優先級繼承的二值信號量,在同步的應用中(任務與任務或中斷與任務之間的同步)二值信號量最適合。互斥信號量適合用於那些需要互斥訪問的應用中。在互斥訪問中互斥信號量相當於一個鑰匙,當任務想要使用資源的時候就必須先獲得這個鑰匙,當使用完資源以後就必須歸還這個鑰匙,這樣其他的任務就可以拿着這個鑰匙去使用資源。互斥信號量使用和二值信號量相同的
API 操作函數,所以互斥信號量也可以設置阻塞時間,不同於二值信號量的是互斥信號量具有優先級繼承的特性。當一個互斥信號量正在被一個低優先級的任務使用,而此時有個高優先級的任務也嘗試獲取這個互斥信號量的話就會被阻塞。不過這個高優先級的任務會將低優先級任務的優先級提升到與自己相同的優先級,這個過程就是優先級繼承。優先級繼承儘可能的降低了高優先級任務處於阻塞態的時間,並且將已經出現的“優先級翻轉”的影響降到最低。優先級繼承並不能完全的消除優先級翻轉,它只是儘可能的降低優先級翻轉帶來的影響。硬實時應用應該在設計之初就要避免優先級翻轉的發生。互斥信號量不能用於中斷服務函數中,原因如下:
● 互斥信號量有優先級繼承的機制,所以只能用在任務中,不能用於中斷服務函數。
● 中斷服務函數中不能因爲要等待互斥信號量而設置阻塞時間進入阻塞態。
3.2 創建互斥信號量
FreeRTOS 提供了兩個互斥信號量創建函數,如表 14.8.2.1 所示:
1 、函數 xSemaphoreCreateMutex()
此函數用於創建一個互斥信號量,所需要的內存通過動態內存管理方法分配。此函數本質是一個宏,真正完成信號量創建的是函數 xQueueCreateMutex(),此函數原型如下:
SemaphoreHandle_t xSemaphoreCreateMutex( void )
參數:
無。
返回值:
NULL: 互斥信號量創建失敗。
其他值: 創建成功的互斥信號量的句柄。
2 、函數 xSemaphoreCreateMutexStatic()
此函數也是創建互斥信號量的,只不過使用此函數創建互斥信號量的話信號量所需要的RAM 需要由用戶來分配,此函數是個宏,具體創建過程是通過函數 xQueueCreateMutexStatic ()來完成的,函數原型如下:
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer )
參數:
pxMutexBuffer :此參數指向一個 StaticSemaphore_t 類型的變量,用來保存信號量結構體。
返回值:
NULL: 互斥信號量創建失敗。
其他值: 創建成功的互斥信號量的句柄。
3.3 互斥信號量創建過程分析
這裏只分析動態創建互斥信號量函數 xSemaphoreCreateMutex (),此函數是個宏,定義如下:
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
可以看出,真正幹事的是函數 xQueueCreateMutex(),此函數在文件 queue.c 中有如下定義,
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,\ (1)
ucQueueType );
prvInitialiseMutex( pxNewQueue ); (2)
return pxNewQueue;
}
(1)、調用函數 xQueueGenericCreate()創建一個隊列,隊列長度爲 1,隊列項長度爲 0,隊列類型爲參數 ucQueueType。由於本函數是創建互斥信號量的,所以參數 ucQueueType 爲queueQUEUE_TYPE_MUTEX。
(2)、調用函數 prvInitialiseMutex()初始化互斥信號量。
函數 prvInitialiseMutex()代碼如下:
static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
if( pxNewQueue != NULL )
{
//雖然創建隊列的時候會初始化隊列結構體的成員變量,但是此時創建的是互斥
//信號量,因此有些成員變量需要重新賦值,尤其是那些用於優先級繼承的。
pxNewQueue->pxMutexHolder = NULL; (1)
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX; (2)
//如果是遞歸互斥信號量的話。
pxNewQueue->u.uxRecursiveCallCount = 0; (3)
traceCREATE_MUTEX( pxNewQueue );
//釋放互斥信號量
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U,\
queueSEND_TO_BACK );
}
else
{
traceCREATE_MUTEX_FAILED();
}
}
(1)和(2)、這裏大家可能會疑惑,隊列結構體 Queue_t 中沒有 pxMutexHolder 和 uxQueueType這兩個成員變量吖?這兩個東西是哪裏來的妖孽?這兩個其實是宏,專門爲互斥信號量準備的,在文件 queue.c 中有如下定義:
#define pxMutexHolder pcTail
#define uxQueueType pcHead
#define queueQUEUE_IS_MUTEX NULL
當 Queue_t 用於表示隊列的時候 pcHead 和 pcTail 指向隊列的存儲區域,當 Queue_t 用於表示互斥信號量的時候就不需要 pcHead 和 pcTail 了。當用於互斥信號量的時候將 pcHead 指向NULL 來表示 pcTail 保存着互斥隊列的所有者,pxMutexHolder 指向擁有互斥信號量的那個任務的任務控制塊。重命名
pcTail 和 pcHead 就是爲了增強代碼的可讀性。
(3)、如果創建的互斥信號量是互斥信號量的話,還需要初始化隊列結構體中的成員變量u.uxRecursiveCallCount。互斥信號量創建成功以後會調用函數xQueueGenericSend()釋放一次信號量,說明互斥信號量默認就是有效的!互斥信號量創建完成以後如圖 14.8.3.1 所示:
3.4 釋放互斥信號量
釋 放 互 斥 信 號 量 的 時 候 和 二 值 信 號 量 、 計 數 型 信 號 量 一 樣 , 都 是 用 的 函 數xSemaphoreGive()(實際上完成信號量釋放的是函數 xQueueGenericSend())。不過由於互斥信號量涉及到優先級繼承的問題,所以具體處理過程會有點區別。使用函數 xSemaphoreGive()釋放信號
量 最 重 要 的 一 步 就 是 將 uxMessagesWaiting 加 一 , 而 這 一 步 就 是 通 過 函 數prvCopyDataToQueue() 來 完 成 的 , 釋 放 信 號 量 的 函 數 xQueueGenericSend() 會 調 用
prvCopyDataToQueue()。互斥信號量的優先級繼承也是在函數 prvCopyDataToQueue()中完成的,此函數中有如下一段代碼:
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,
const void * pvItemToQueue,
const BaseType_t xPosition )
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;
uxMessagesWaiting = pxQueue->uxMessagesWaiting;
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
{
#if ( configUSE_MUTEXES == 1 ) //互斥信號量
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) (1)
{
xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );(2)
pxQueue->pxMutexHolder = NULL; (3)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
/*********************************************************************/
/*************************省略掉其他處理代碼**************************/
/*********************************************************************/
pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;
return xReturn;
}
(1)、當前操作的是互斥信號量。
(2)、調用函數 xTaskPriorityDisinherit()處理互斥信號量的優先級繼承問題。
(3)、互斥信號量釋放以後,互斥信號量就不屬於任何任務了,所以 pxMutexHolder 要指向NULL。
在來看一下函數 xTaskPriorityDisinherit()是怎麼具體的處理優先級繼承的,函數xTaskPriorityDisinherit()代碼如下:
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
BaseType_t xReturn = pdFALSE;
if( pxMutexHolder != NULL ) (1)
{
//當一個任務獲取到互斥信號量以後就會涉及到優先級繼承的問題,正在釋放互斥
//信號量的任務肯定是當前正在運行的任務 pxCurrentTCB。
configASSERT( pxTCB == pxCurrentTCB );
configASSERT( pxTCB->uxMutexesHeld );
( pxTCB->uxMutexesHeld )--; (2)
//是否存在優先級繼承?如果存在的話任務當前優先級肯定和任務基優先級不同。
if( pxTCB->uxPriority != pxTCB->uxBasePriority ) (3)
{
//當前任務只獲取到了一個互斥信號量
if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 ) (4)
{
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) (5)
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority ); (6)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//使用新的優先級將任務重新添加到就緒列表中
traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
pxTCB->uxPriority = pxTCB->uxBasePriority; (7)
/* Reset the event list item value. It cannot be in use for any other purpose if this task is running, and it must be running to give back the mutex. */
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), \ (8)
( TickType_t ) configMAX_PRIORITIES - \
( TickType_t ) pxTCB->uxPriority );
prvAddTaskToReadyList( pxTCB ); (9)
xReturn = pdTRUE; (10)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
(1)、函數的參數 pxMutexHolder 表示擁有此互斥信號量任務控制塊,所以要先判斷此互斥信號量是否已經被其他任務獲取。
(2)、有的任務可能會獲取多個互斥信號量,所以就需要標記任務當前獲取到的互斥信號量個數,任務控制塊結構體的成員變量 uxMutexesHeld 用來保存當前任務獲取到的互斥信號量個數。任務每釋放一次互斥信號量,變量 uxMutexesHeld 肯定就要減一。
(3)、判斷是否存在優先級繼承,如果存在的話任務的當前優先級肯定不等於任務的基優先級。
(4)、判斷當前釋放的是不是任務所獲取到的最後一個互斥信號量,因爲如果任務還獲取了其他互斥信號量的話就不能處理優先級繼承。優先級繼承的處理必須是在釋放最後一個互斥信號量的時候。
(5)、優先級繼承的處理說白了就是將任務的當前優先級降低到任務的基優先級,所以要把當前任務先從任務就緒表中移除。當任務優先級恢復爲原來的優先級以後再重新加入到就緒表中。
(6)、如果任務繼承來的這個優先級對應的就緒表中沒有其他任務的話就將取消這個優先級的就緒態。
(7)、重新設置任務的優先級爲任務的基優先級 uxBasePriority。
(8)、復位任務的事件列表項。
(9)、將優先級恢復後的任務重新添加到任務就緒表中。
(10)、返回 pdTRUE,表示需要進行任務調度。
3.5 獲取互斥信號量
獲取互斥信號量的函數同獲取二值信號量和計數型信號量的函數相同,都是xSemaphoreTake()(實際執行信號量獲取的函數是 xQueueGenericReceive()),獲取互斥信號量的過程也需要處理優先級繼承的問題,函數 xQueueGenericReceive()在文件 queue.c 中有定義,在第十三章講解隊列的時候我們沒有分析這個函數,本節就來簡單的分析一下這個函數,縮減後的函數代碼如下:
BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t
xTicksToWait, const BaseType_t xJustPeeking )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
int8_t *pcOriginalReadPosition;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
for( ;; )
{
taskENTER_CRITICAL();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
//判斷隊列是否有消息
if( uxMessagesWaiting > ( UBaseType_t ) 0 ) (1)
{
pcOriginalReadPosition = pxQueue->u.pcReadFrom;
prvCopyDataFromQueue( pxQueue, pvBuffer ); (2)
if( xJustPeeking == pdFALSE ) (3)
{
traceQUEUE_RECEIVE( pxQueue );
//移除消息
pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1; (4)
#if ( configUSE_MUTEXES == 1 ) (5)
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
pxQueue->pxMutexHolder = (6)
( int8_t * ) pvTaskIncrementMutexHeldCount();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
//查看是否有任務因爲入隊而阻塞,如果有的話就需要解除阻塞態。
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == (7)
pdFALSE )
{
if( xTaskRemoveFromEventList( &
( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
//如果解除阻塞的任務優先級比當前任務優先級高的話就需要
//進行一次任務切換
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else (8)
{
traceQUEUE_PEEK( pxQueue );
//讀取隊列中的消息以後需要刪除消息
pxQueue->u.pcReadFrom = pcOriginalReadPosition;
//如果有任務因爲出隊而阻塞的話就解除任務的阻塞態。
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == (9)
pdFALSE )
{
if( xTaskRemoveFromEventList( &
( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
//如果解除阻塞的任務優先級比當前任務優先級高的話就需要
//進行一次任務切換
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
else //隊列爲空 (10)
{
if( xTicksToWait == ( TickType_t ) 0 )
{
//隊列爲空,如果阻塞時間爲 0 的話就直接返回 errQUEUE_EMPTY
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
//隊列爲空並且設置了阻塞時間,需要初始化時間狀態結構體。
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();
prvLockQueue( pxQueue );
//更新時間狀態結構體,並且檢查超時是否發生
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) (11)
{
if( prvIsQueueEmpty( pxQueue ) != pdFALSE ) (12)
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) (13)
{
taskENTER_CRITICAL();
{
vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );(14)
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), (15)
xTicksToWait );
prvUnlockQueue( pxQueue );
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
//重試一次
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
(1)、隊列不爲空,可以從隊列中提取數據。
(2)、調用函數 prvCopyDataFromQueue()使用數據拷貝的方式從隊列中提取數據。
(3)、數據讀取以後需要將數據刪除掉。
(4)、隊列的消息數量計數器 uxMessagesWaiting 減一,通過這一步就將數據刪除掉了。
(5)、表示此函數是用於獲取互斥信號量的。
(6)、獲取互斥信號量成功,需要標記互斥信號量的所有者,也就是給 pxMutexHolder 賦值,pxMutexHolder 應 該 是 當 前 任 務 的 任 務 控 制 塊 。 但 是 這 裏 是 通 過 函 數pvTaskIncrementMutexHeldCount()來賦值的,此函數很簡單,只是將任務控制塊中的成員變量uxMutexesHeld
加一,表示任務獲取到了一個互斥信號量,最後此函數返回當前任務的任務控制塊。
(7)、出隊成功以後判斷是否有任務因爲入隊而阻塞的,如果有的話就需要解除任務的阻塞態,如果解除阻塞的任務優先級比當前任務的優先級高還需要進行一次任務切換。
(8)、出隊的時候不需要刪除消息。
(9)、如果出隊的時候不需要刪除消息的話那就相當於剛剛出隊的那條消息接着有效!既然還有有效的消息存在隊列中,那麼就判斷是否有任務因爲出隊而阻塞,如果有的話就解除任務的阻塞態。同樣的,如果解除阻塞的任務優先級比當前任務的優先級高的話還需要進行一次任務切換。
(10)、上面分析的都是隊列不爲空的時候,那當隊列爲空的時候該如何處理呢?處理過程和隊列的任務級通用入隊函數 xQueueGenericSend()類似。如果阻塞時間爲 0 的話就就直接返回errQUEUE_EMPTY,表示隊列空,如果設置了阻塞時間的話就進行相關的處理。
(11)、檢查超時是否發生,如果沒有的話就需要將任務添加到隊列的 xTasksWaitingToReceive列表中。
(12)、檢查隊列是否繼續爲空?如果不爲空的話就會在重試一次出隊。
(13)、表示此函數是用於獲取互斥信號量的。
(14)、調用函數 vTaskPriorityInherit()處理互斥信號量中的優先級繼承問題,如果函數xQueueGenericReceive()用於獲取互斥信號量的話,此函數執行到這裏說明互斥信號量正在被其他的任務佔用。此函數和
14.8.4 小節中的函數 xTaskPriorityDisinherit()過程相反。此函數會判斷當前任務的任務優先級是否比正在擁有互斥信號量的那個任務的任務優先級高,如果是的話就會把擁有互斥信號量的那個低優先級任務的優先級調整爲與當前任務相同的優先級!
(15)、經過(12)步判斷,隊列依舊爲空,那麼就將任務添加到列表 xTasksWaitingToReceive中。在上面的分析中,紅色部分就是當函數 xQueueGenericReceive()用於互斥信號量的時候的處理過程,其中(13)和(14)條詳細的分析了互斥信號量優先級繼承的過程。我們舉個例子來簡單的演示一下這個過程,假設現在有兩個任務
HighTask 和 LowTask,HighTask 的任務優先級爲 4,LowTask 的任務優先級爲 2。這兩個任務都會操同一個互斥信號量 Mutex,LowTask 先獲取到互斥信號量 Mutex。此時任務 HighTask 也要獲取互斥信號量 Mutex,任務 HighTask 調用函數xSemaphoreTake()嘗試獲取互斥信號量
Mutex,發現此互斥信號量正在被任務 LowTask 使用,並且 LowTask 的任務優先級爲 2,比自己的任務優先級小,因爲任務 HighTask 就會將 LowTask的任務優先級調整爲與自己相同的優先級,即 4,然後任務 HighTask 進入阻塞態等待互斥信號量有效。
3.6 遞歸互斥信號量
3.6.1 遞歸互斥信號量簡介
遞歸互斥信號量可以看作是一個特殊的互斥信號量,已經獲取了互斥信號量的任務就不能再次獲取這個互斥信號量,但是遞歸互斥信號量不同,已經獲取了遞歸互斥信號量的任務可以再次獲取這個遞歸互斥信號量,而且次數不限!一個任務使用函數 xSemaphoreTakeRecursive()成功的獲取了多少次遞歸互斥信號量就得使用函數
xSemaphoreGiveRecursive()釋放多少次!比如某個任務成功的獲取了 5 次遞歸信號量,那麼這個任務也得同樣的釋放 5 次遞歸信號量。遞歸互斥信號量也有優先級繼承的機制,所以當任務使用完遞歸互斥信號量以後一定要記得釋放。同互斥信號量一樣,遞歸互斥信號量不能用在中斷服務函數中。
● 由於優先級繼承的存在,就限定了遞歸互斥信號量只能用在任務中,不能用在中斷服務函數中!
● 中斷服務函數不能設置阻塞時間。要使用遞歸互斥信號量的話宏 configUSE_RECURSIVE_MUTEXES 必須爲 1!
3.6.2 創建互斥信號量
FreeRTOS 提供了兩個互斥信號量創建函數,如表 14.10.2.1 所示:
1 、函數 xSemaphoreCreateRecursiveMutex()
此函數用於創建一個遞歸互斥信號量,所需要的內存通過動態內存管理方法分配。此函數本質是一個宏,真正完成信號量創建的是函數 xQueueCreateMutex (),此函數原型如下:
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )
參數:
無。
返回值:
NULL: 互斥信號量創建失敗。
其他值: 創建成功的互斥信號量的句柄。
2 、函數 xSemaphoreCreateRecursiveMutexStatic()
此函數也是創建遞歸互斥信號量的,只不過使用此函數創建遞歸互斥信號量的話信號量所需 要 的 RAM 需 要 由 用 戶 來 分 配 , 此 函 數 是 個 宏 , 具 體 創 建 過 程 是 通 過 函 數
xQueueCreateMutexStatic ()來完成的,函數原型如下:
SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic( StaticSemaphore_t *pxMutexBuffer )
參數:
pxMutexBuffer :此參數指向一個 StaticSemaphore_t 類型的變量,用來保存信號量結構體。
返回值:
NULL: 互斥信號量創建失敗。
其他值: 創建成功的互斥信號量的句柄。
3.6.3 遞歸信號量創建過程分析
這裏只分析動態創建互斥信號量函數 xSemaphoreCreateRecursiveMutex (),此函數是個宏,
定義如下:
#define xSemaphoreCreateRecursiveMutex()
xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
可以看出,真正幹事的是函數 xQueueCreateMutex(),互斥信號量的創建也是用的這個函數,
只是在創建遞歸互斥信號量的時候類型選擇爲 queueQUEUE_TYPE_RECURSIVE_MUTEX。具
體的創建過程參考 14.8.3 小節。
3.6.4 釋放遞歸互斥信號量
遞歸互斥信號量有專用的釋放函數:xSemaphoreGiveRecursive(),此函數爲宏,如下:
#define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) )
函 數 的 參 數 就 是 就 是 要 釋 放 的 遞 歸 互 斥 信 號 量 , 真 正 的 釋 放 是 由 函 數
xQueueGiveMutexRecursive()來完成的,此函數代碼如下:
BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
configASSERT( pxMutex );
//檢查遞歸互斥信號量是不是被當前任務獲取的,要釋放遞歸互斥信號量的任務肯定是當
//前正在運行的任務。 因爲同互斥信號量一樣,遞歸互斥信號量的獲取和釋放要在同一個
//任務中完成!如果當前正在運行的任務不是遞歸互斥信號量的擁有者就不能釋放!
if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() ) (1)
{
traceGIVE_MUTEX_RECURSIVE( pxMutex );
( pxMutex->u.uxRecursiveCallCount )--; (2)
if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 ) (3)
{
( void ) xQueueGenericSend( pxMutex, NULL, \ (4)
queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
xReturn = pdPASS; (5)
}
else
{
xReturn = pdFAIL; (6)
traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
return xReturn;
}
(1)、哪個任務獲取到的遞歸互斥信號量,哪個任務就釋放!要釋放遞歸互斥信號量的任務肯定是當前正在運行的任務。檢查這個任務是不是遞歸互斥信號量的擁有者,如果不是的話就不能完成釋放。
(2)、uxRecursiveCallCount 減一,uxRecursiveCallCount 用來記錄遞歸信號量被獲取的次數。由於遞歸互斥信號量可以被一個任務多次獲取,因此在釋放的時候也要多次釋放,但是只有在最後一次釋放的時候纔會調用函數xQueueGenericSend()完成釋放過程,其他的時候只是簡單的將
uxRecursiveCallCount 減一即可。
(3)、當 uxRecursiveCallCount 爲 0 的時候說明是最後一次釋放了。
(4)、如果是最後一次釋放的話就調用函數 xQueueGenericSend()完成真正的釋放過程。阻塞時間是queueMUTEX_GIVE_BLOCK_TIME,宏 queueMUTEX_GIVE_BLOCK_TIME 爲 0。
(5)、遞歸互斥信號量釋放成功,返回 pdPASS。
(6)、遞歸互斥信號量釋放未成功,返回 pdFAIL。
由於遞歸互斥信號量可以被一個任務重複的獲取,因此在釋放的時候也要釋放多次,但是
只有在最後一次釋放的時候纔會調用函數 xQueueGenericSend()完成真正的釋放。其他釋放的話
只是簡單的將 uxRecursiveCallCount 減一。
3.6.5 獲取遞歸互斥信號量
遞歸互斥信號量的獲取使用函數 xSemaphoreTakeRecursive(),此函數是個宏,定義如下:
#define xSemaphoreTakeRecursive( xMutex, xBlockTime )
xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
函數第一個參數是要獲取的遞歸互斥信號量句柄,第二個參數是阻塞時間。真正的獲取過程是由函數 xQueueTakeMutexRecursive()來完成的,此函數如下:
BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, //要獲取的信號量
TickType_t xTicksToWait )//阻塞時間
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
configASSERT( pxMutex );
traceTAKE_MUTEX_RECURSIVE( pxMutex );
if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() ) (1)
{
( pxMutex->u.uxRecursiveCallCount )++; (2)
xReturn = pdPASS;
}
else
{
xReturn = xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE ); (3)
if( xReturn != pdFAIL )
{
( pxMutex->u.uxRecursiveCallCount )++; (4)
}
else
{
raceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
}
return xReturn;
}
(1)、判斷當前要獲取遞歸互斥信號量的任務是不是已經是遞歸互斥信號量的擁有者。通過這一步就可以判斷出當前任務是第一次獲取遞歸互斥信號量還是重複獲取。
(2)、如果當前任務已經是遞歸互斥信號量的擁有者,那就說明任務已經獲取了遞歸互斥信號量,本次是重複獲取遞歸互斥信號量,那麼就簡單的將 uxRecursiveCallCount 加一,然後返回pdPASS 表示獲取成功。
(3)、如果任務是第一次獲取遞歸互斥信號量的話就需要調用函數 xQueueGenericReceive()完成真正的獲取過程。
(4)、第一次獲取遞歸互斥信號量成功以後將 uxRecursiveCallCount 加一。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.