您當前的位置:首頁 > 遊戲

FreeRTOS互斥訊號量

作者:由 安迪西 發表于 遊戲時間:2022-09-20

互斥訊號量

1。 優先順序翻轉

優先順序翻轉是使用二值訊號量時常遇見的問題,在可剝奪核心中非常常見,但是在實時系統中不允許出現這種現象,因為會破壞任務的預期順序,可能會導致嚴重後果。

如下圖所示的優先順序翻轉的例子:

低優先順序任務L要訪問共享資源,在獲取到訊號量使用CPU的過程中,如果此時高優先順序任務H到達,會剝奪L的CPU使用權從而執行任務H

當H想要訪問共享資源時,由於此時該資源的訊號量仍被L佔用著,H只能掛起等待L釋放該訊號量

L繼續執行,此時中優先順序任務M到達,再次剝奪L的CPU使用權從而執行任務M

M執行完後,將CPU使用權歸還給任務L,L繼續執行

L執行完畢並釋放出了訊號量,至此高優先順序的任務H才能獲取到該訊號量訪問共享資源並執行

FreeRTOS互斥訊號量

由上圖可見,任務H的優先順序實際上降到了任務L的優先順序水平,因為要一直等待L釋放其佔用的共享資源。過程中不需要使用共享資源的中優先順序任務M搶佔CPU使用權後順利執行完成,這樣就相當於M的優先順序高於J,導致優先順序翻轉

2。 互斥訊號量

互斥訊號量其實就是一個擁有優先順序繼承的二值訊號量。二值訊號適合於同步應用中,互斥訊號適用於需要互斥訪問的應用中。互斥訪問中互斥訊號量相當於一個鑰匙,當任務想要使用資源的時候就必須先獲得這個鑰匙,使用完資源後必須歸還這個鑰匙

當一個互斥訊號量正被一個低優先順序任務使用時,若有高優先順序的任務也嘗試獲取該互斥訊號量的話就會被阻塞。不過此時高優先順序任務會將低優先順序任務的優先順序提升到與與自已相同的等級(即優先順序繼承)。優先順序繼承只是儘可能降低優先順序翻轉帶來的影響,並不能完全消除優先順序翻轉

FreeRTOS互斥訊號量

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互斥訊號量

動態建立互斥訊號量

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互斥訊號量

關注我的公眾號,在公眾號裡發如下訊息,即可獲取相應的工程原始碼:

FreeRTOS互斥訊號量例項