FreeRTOS互斥訊號量
互斥訊號量
1。 優先順序翻轉
優先順序翻轉是使用二值訊號量時常遇見的問題,在可剝奪核心中非常常見,但是在實時系統中不允許出現這種現象,因為會破壞任務的預期順序,可能會導致嚴重後果。
如下圖所示的優先順序翻轉的例子:
低優先順序任務L要訪問共享資源,在獲取到訊號量使用CPU的過程中,如果此時高優先順序任務H到達,會剝奪L的CPU使用權從而執行任務H
當H想要訪問共享資源時,由於此時該資源的訊號量仍被L佔用著,H只能掛起等待L釋放該訊號量
L繼續執行,此時中優先順序任務M到達,再次剝奪L的CPU使用權從而執行任務M
M執行完後,將CPU使用權歸還給任務L,L繼續執行
L執行完畢並釋放出了訊號量,至此高優先順序的任務H才能獲取到該訊號量訪問共享資源並執行
由上圖可見,任務H的優先順序實際上降到了任務L的優先順序水平,因為要一直等待L釋放其佔用的共享資源。過程中不需要使用共享資源的中優先順序任務M搶佔CPU使用權後順利執行完成,這樣就相當於M的優先順序高於J,導致優先順序翻轉
2。 互斥訊號量
互斥訊號量其實就是一個擁有優先順序繼承的二值訊號量。二值訊號適合於同步應用中,互斥訊號適用於需要互斥訪問的應用中。互斥訪問中互斥訊號量相當於一個鑰匙,當任務想要使用資源的時候就必須先獲得這個鑰匙,使用完資源後必須歸還這個鑰匙
當一個互斥訊號量正被一個低優先順序任務使用時,若有高優先順序的任務也嘗試獲取該互斥訊號量的話就會被阻塞。不過此時高優先順序任務會將低優先順序任務的優先順序提升到與與自已相同的等級(即優先順序繼承)。優先順序繼承只是儘可能降低優先順序翻轉帶來的影響,並不能完全消除優先順序翻轉
3。 互斥訊號量的API函式
3。1 建立互斥訊號量
/********************動態建立互斥訊號量**********************************************/
SemaphoreHandle_t
xSemaphoreCreateMutex
(
void
)
/********************靜態建立互斥訊號量**********************************************/
SemaphoreHandle_t
xSemaphoreCreateMutexStatic
(
StaticSemaphore_t
*
pxSemaphoreBuffer
)
//引數:pxSemaphoreBuffer 指向一個StaticSemaphore_t型別的變數,用來儲存訊號量結構體
/***********************************************************************************/
返回值:建立成功返回互斥訊號量控制代碼;失敗返回
NULL
動態互斥訊號量建立函式是一個宏,最終是透過xQueueCreateMutex()函式來完成,其原始碼如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)
#endif
/**************xQueueCreateMutex原始碼分析*********************/
QueueHandle_t
xQueueCreateMutex
(
const
uint8_t
ucQueueType
){
Queue_t
*
pxNewQueue
;
const
UBaseType_t
uxMutexLength
=
(
UBaseType_t
)
1
,
uxMutexSize
=
(
UBaseType_t
)
0
;
/* 建立一個佇列長度為1,佇列項長度為0,佇列型別為queueQUEUE_TYPE_MUTEX的佇列 */
pxNewQueue
=
(
Queue_t
*
)
xQueueGenericCreate
(
uxMutexLength
,
uxMutexSize
,
ucQueueType
);
/* 初始化互斥訊號量,其實就是初始化訊息佇列的控制塊 */
prvInitialiseMutex
(
pxNewQueue
);
return
pxNewQueue
;
}
/**************prvInitialiseMutex原始碼分析*********************/
static
void
prvInitialiseMutex
(
Queue_t
*
pxNewQueue
){
if
(
pxNewQueue
!=
NULL
){
/* 對建立好的佇列結構體的某些互斥訊號量特有的成員變數重新賦值 */
pxNewQueue
->
pxMutexHolder
=
NULL
;
//互斥訊號量專有的宏
pxNewQueue
->
uxQueueType
=
queueQUEUE_IS_MUTEX
;
//互斥訊號量專有的宏
/* 如果是遞迴互斥訊號量,將以下成員變數賦值為0 */
pxNewQueue
->
u
。
uxRecursiveCallCount
=
0
;
traceCREATE_MUTEX
(
pxNewQueue
);
/* 釋放互斥訊號量 */
(
void
)
xQueueGenericSend
(
pxNewQueue
,
NULL
,(
TickType_t
)
0U
,
queueSEND_TO_BACK
);
}
else
{
traceCREATE_MUTEX_FAILED
();
}
}
3。2 釋放互斥訊號量
釋放互斥訊號量函式與二值訊號量、計數訊號量釋放函式是一樣的。但是由於互斥訊號涉及到優先順序繼承的問題,所以處理過程會有一些區別。
訊號量釋放函式xSemaphoreGive()實際上呼叫xQueueGenericSend()函式,分析該函式原始碼(訊息佇列中有介紹)可見函式會呼叫prvCopyDataToQueue()函式。互斥訊號量的優先順序繼承就是在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
){
/* 呼叫以下函式處理優先順序繼承問題 */
xReturn
=
xTaskPriorityDisinherit
((
void
*
)
pxQueue
->
pxMutexHolder
);
pxQueue
->
pxMutexHolder
=
NULL
;
//互斥訊號量釋放後,就不屬於任何任務了
}
else
{
mtCOVERAGE_TEST_MARKER
();
}
}
#endif
/* configUSE_MUTEXES */
}
/*********************************************************/
/********************省略其他處理程式碼*********************/
/*********************************************************/
pxQueue
->
uxMessagesWaiting
=
uxMessagesWaiting
+
(
UBaseType_t
)
1
;
return
xReturn
}
優先順序處理函式xTaskPriorityDisinherit()的原始碼分析如下:
BaseType_t
xTaskPriorityDisinherit
(
TaskHandle_t
const
pxMutexHolder
){
TCB_t
*
const
pxTCB
=
(
TCB_t
*
)
pxMutexHolder
;
BaseType_t
xReturn
=
pdFALSE
;
if
(
pxMutexHolder
!=
NULL
){
/* 任務獲取到互斥訊號量後就會涉及到優先順序繼承 */
configASSERT
(
pxTCB
==
pxCurrentTCB
);
configASSERT
(
pxTCB
->
uxMutexesHeld
);
(
pxTCB
->
uxMutexesHeld
)
——
;
//用於標記任務當前獲取到的互斥訊號量個數
/* 如果任務當前優先順序和任務基優先順序不同,則存在優先順序繼承 */
if
(
pxTCB
->
uxPriority
!=
pxTCB
->
uxBasePriority
){
/* 當前任務只獲取到一個互斥訊號量 */
if
(
pxTCB
->
uxMutexesHeld
==
(
UBaseType_t
)
0
){
/* 把任務的當前優先順序降低到基優先順序 */
if
(
uxListRemove
(
&
(
pxTCB
->
xStateListItem
))
==
(
UBaseType_t
)
0
){
taskRESET_READY_PRIORITY
(
pxTCB
->
uxPriority
);
}
else
{
mtCOVERAGE_TEST_MARKER
();
}
/* 使用新的優先順序將任務新增到就緒列表中 */
traceTASK_PRIORITY_DISINHERIT
(
pxTCB
,
pxTCB
->
uxBasePriority
);
pxTCB
->
uxPriority
=
pxTCB
->
uxBasePriority
;
/* 復位任務的事件列表項 */
listSET_LIST_ITEM_VALUE
(
&
(
pxTCB
->
xEventListItem
),
\
(
TickType_t
)
configMAX_PRIORITIES
-
(
TickType_t
)
pxTCB
->
uxPriority
);
prvAddTaskToReadyList
(
pxTCB
);
//將恢復優先順序後的任務重新新增到就緒表
xReturn
=
pdTRUE
;
//返回pdTRUE,表示需要進行任務排程
}
else
{
mtCOVERAGE_TEST_MARKER
();
}
}
else
{
mtCOVERAGE_TEST_MARKER
();
}
}
else
{
mtCOVERAGE_TEST_MARKER
();
}
return
xReturn
;
}
3。3 獲取互斥訊號量
互斥訊號量獲取函式與二值訊號量、計數訊號量獲取函式是一樣的,都是xSemaphoreTake()函式,最終呼叫xQueueGenericReceive();獲取互斥訊號量的過程也需要處理優先順序繼承的問題,原始碼分析如下示
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
){
/* 如有資料,呼叫以下函式使用資料複製的方式從佇列中提取資料 */
pcOriginalReadPosition
=
pxQueue
->
u
。
pcReadFrom
;
prvCopyDataFromQueue
(
pxQueue
,
pvBuffer
);
if
(
xJustPeeking
==
pdFALSE
){
//資料讀取後需要將資料刪除
traceQUEUE_RECEIVE
(
pxQueue
);
/* 移除訊息 */
pxQueue
->
uxMessagesWaiting
=
uxMessagesWaiting
-
1
;
#if ( configUSE_MUTEXES == 1 )
{
if
(
pxQueue
->
uxQueueType
==
queueQUEUE_IS_MUTEX
){
/* 若獲取訊號量成功,則標記互斥訊號量的所有者 */
pxQueue
->
pxMutexHolder
=
(
int8_t
*
)
pvTaskIncrementMutexHeldCount
();
}
else
{
mtCOVERAGE_TEST_MARKER
();
}
}
#endif
/* configUSE_MUTEXES */
/* 檢視是否有任務因為入隊而阻塞,若有就解除阻塞態 */
if
(
listLIST_IS_EMPTY
(
&
(
pxQueue
->
xTasksWaitingToSend
))
==
pdFALSE
){
if
(
xTaskRemoveFromEventList
(
&
(
pxQueue
->
xTasksWaitingToSend
))
!=
pdFALSE
){
/* 若解除阻塞的任務優先順序比當前任務高,就需要進行一次任務切換 */
queueYIELD_IF_USING_PREEMPTION
();
}
else
{
mtCOVERAGE_TEST_MARKER
();
}
}
else
{
mtCOVERAGE_TEST_MARKER
();
}
}
else
{
//資料讀取後不需要將資料刪除
traceQUEUE_PEEK
(
pxQueue
);
pxQueue
->
u
。
pcReadFrom
=
pcOriginalReadPosition
;
/* 檢視是否有任務因為出隊而阻塞,若有就解除阻塞態 */
if
(
listLIST_IS_EMPTY
(
&
(
pxQueue
->
xTasksWaitingToReceive
))
==
pdFALSE
){
if
(
xTaskRemoveFromEventList
(
&
(
pxQueue
->
xTasksWaitingToReceive
)
)
!=
pdFALSE
){
/* 若解除阻塞的任務優先順序比當前任務高,就需要進行一次任務切換 */
queueYIELD_IF_USING_PREEMPTION
();
}
else
{
mtCOVERAGE_TEST_MARKER
();
}
}
else
{
mtCOVERAGE_TEST_MARKER
();
}
}
taskEXIT_CRITICAL
();
return
pdPASS
;
}
else
{
//若佇列為空
if
(
xTicksToWait
==
(
TickType_t
)
0
){
/* 如阻塞時間為0,則直接返回errQUEUE_EMPTY */
taskEXIT_CRITICAL
();
traceQUEUE_RECEIVE_FAILED
(
pxQueue
);
return
errQUEUE_EMPTY
;
}
else
if
(
xEntryTimeSet
==
pdFALSE
){
/* 若阻塞時間不為0,則初始化時間狀態結構體 */
vTaskSetTimeOutState
(
&
xTimeOut
);
xEntryTimeSet
=
pdTRUE
;
}
else
{
/* Entry time was already set。 */
mtCOVERAGE_TEST_MARKER
();
}
}
}
taskEXIT_CRITICAL
();
vTaskSuspendAll
();
prvLockQueue
(
pxQueue
);
/* 更新時間狀態結構體,檢查是否超時 */
if
(
xTaskCheckForTimeOut
(
&
xTimeOut
,
&
xTicksToWait
)
==
pdFALSE
){
/* 檢查佇列是否為空 */
if
(
prvIsQueueEmpty
(
pxQueue
)
!=
pdFALSE
){
//若佇列為空
traceBLOCKING_ON_QUEUE_RECEIVE
(
pxQueue
);
#if ( configUSE_MUTEXES == 1 )
{
if
(
pxQueue
->
uxQueueType
==
queueQUEUE_IS_MUTEX
){
taskENTER_CRITICAL
();
{
/* 處理互斥訊號量的優先順序繼承 */
vTaskPriorityInherit
(
(
void
*
)
pxQueue
->
pxMutexHolder
);
}
taskEXIT_CRITICAL
();
}
else
{
mtCOVERAGE_TEST_MARKER
();
}
}
#endif
/* 由於佇列為空,將任務新增到xTasksWaitingToReceive列表中 */
vTaskPlaceOnEventList
(
&
(
pxQueue
->
xTasksWaitingToReceive
),
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
();
}
}
}
}
4。 互斥訊號量的應用例項
本例項介紹並模擬如何使用互斥訊號量來避免章節1中的優先順序翻轉現象
使用STM32CubeMX將FreeRTOS移植到工程中,建立優先順序為高中低的三個任務、一個互斥訊號量
High_Task:高優先順序任務,會獲取互斥訊號量,獲取成功後進行相應的處理,處理完後釋放互斥訊號量
Middle_Task:中優先順序任務,簡單的應用任務
Low_Task:低優先順序任務,會獲取互斥訊號量,獲取成功後進行相應的處理,處理完後釋放互斥訊號量。但是任務佔用互斥訊號量的時間比高優先順序任務佔用的時間要長
4。1 STM32CubeMX設定
RCC設定外接HSE,時鐘設定為72M
USART1選擇為非同步通訊方式,波特率設定為115200Bits/s,傳輸資料長度為8Bit,無奇偶校驗,1位停止位;
啟用FreeRTOS,新增任務,設定任務名稱、優先順序、堆疊大小、函式名稱等引數
動態建立互斥訊號量
使用FreeRTOS作業系統,建議將HAL庫的Timebase Source從SysTick改為其他定時器,選好定時器後,系統會自動配置TIM
輸入工程名,選擇路徑(不要有中文),選擇MDK-ARM V5;勾選Generated periphera initialization as a pair of ‘。c/。h’ files per IP ;點選GENERATE CODE,生成工程程式碼
4。2 MDK-ARM軟體程式設計
新增High_Task、Middle_Task、Low_Task任務函式程式碼
/******************HighTask**************************/
void
HighTask
(
void
const
*
argument
){
for
(;;){
vTaskDelay
(
500
);
printf
(
“High task Pend Semaphore
\r\n
”
);
xSemaphoreTake
(
MutexSemHandle
,
portMAX_DELAY
);
printf
(
“High task running!
\r\n
”
);
HAL_GPIO_TogglePin
(
GPIOC
,
GPIO_PIN_1
);
xSemaphoreGive
(
MutexSemHandle
);
vTaskDelay
(
500
);
}
}
/******************MiddleTask***********************/
void
MiddleTask
(
void
const
*
argument
){
for
(;;){
printf
(
“Middle task running!
\r\n
”
);
HAL_GPIO_TogglePin
(
GPIOC
,
GPIO_PIN_0
);
vTaskDelay
(
1000
);
}
}
/******************LowTask**************************/
void
LowTask
(
void
const
*
argument
){
for
(;;){
xSemaphoreTake
(
MutexSemHandle
,
portMAX_DELAY
);
printf
(
“Low task running!
\r\n
”
);
for
(
int
i
=
0
;
i
<
20000000
;
i
++
){
taskYIELD
();
}
xSemaphoreGive
(
MutexSemHandle
);
vTaskDelay
(
1000
);
}
}
4。3 下載驗證
編譯無誤下載到開發板後,開啟串列埠除錯助手,串列埠輸出如下圖示的除錯資訊:
由於High_Task任務延時500ms,因此Middle_Task最先開始執行;
之後Low_Task執行,獲取互斥訊號;
High_Task開始執行,阻塞在請求互斥訊號量這裡,等待Low_Task釋放互斥訊號量
此時由於Low_Task正在使用互斥訊號量,因此Low_Task的優先順序暫時提升到了與High_Task相同的優先順序,所以Middle_Task無法打斷Low_Task的執行。Low_Task執行完後,釋放互斥訊號量,High_Task獲取互斥訊號量並執行
由此可見,互斥訊號量有效的解決了優先順序翻轉的問題
關注我的公眾號,在公眾號裡發如下訊息,即可獲取相應的工程原始碼:
FreeRTOS互斥訊號量例項