FreeRTOS — 互斥信號量**


以下內容轉載自安富萊電子:http://forum.armfly.com/forum.php

1 、互 斥 信 號 量

1.1 互斥信號量的概念及其作用

互斥信號量的主要作用是對資源實現互斥訪問,使用二值信號量也可以實現互斥訪問的功能,不過互斥信號量與二值信號量有區別。下面我們先舉一個通過二值信號量實現資源獨享,即互斥訪問的例子,讓大家有一個形象的認識,進而引出要講解的互斥信號量。

運行條件:  

 讓兩個任務 Task1 和 Task2 都運行串口打印函數 printf,這里我們就通過二值信號量實現對函數printf 的互斥訪問。如果不對函數 printf 進行互斥訪問,串口打印容易出現亂碼。
 用計數信號量實現二值信號量只需將計數信號量的初始值設置為 1 即可。

代碼實現:

   創建二值信號量

static SemaphoreHandle_t xSemaphore = NULL;
/*
*********************************************************************************************************
* 函 數 名: AppObjCreate
* 功能說明: 創建任務通信機制
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
static void AppObjCreate (void)
{
/* 創建二值信號量,首次創建信號量計數值是 0 */
xSemaphore = xSemaphoreCreateBinary();
if(xSemaphore == NULL)
{
/* 沒有創建成功,用戶可以在這里加入創建失敗的處理機制 */
}
/* 先釋放一次,將初始值改為 1,利用二值信號量實現互斥功能 */
xSemaphoreGive(xSemaphore);
}

 通過二值信號量實現對 printf 函數互斥訪問的兩個任務

/*
*********************************************************************************************************
* 函 數 名: vTaskLED
* 功能說明: 實現對串口的互斥訪問
* 形 參: pvParameters 是在創建該任務時傳遞的形參
* 返 回 值: 無
* 優 先 級: 2
*********************************************************************************************************
*/
static void vTaskLED(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 300;
/* 獲取當前的系統時間 */
xLastWakeTime = xTaskGetTickCount();
while(1)
{
/* 通過二值信號量實現資源互斥訪問,永久等待直到資源可用 */
xSemaphoreTake(xSemaphore, portMAX_DELAY);
printf("任務 vTaskLED 在運行\r\n");
bsp_LedToggle(1);
bsp_LedToggle(4);
xSemaphoreGive(xSemaphore);
/* vTaskDelayUntil 是絕對延遲,vTaskDelay 是相對延遲。*/
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
/*
*********************************************************************************************************
* 函 數 名: vTaskMsgPro
* 功能說明: 實現對串口的互斥訪問
* 形 參: pvParameters 是在創建該任務時傳遞的形參
* 返 回 值: 無
* 優 先 級: 3
*********************************************************************************************************
*/
static void vTaskMsgPro(void *pvParameters)
{
  TickType_t xLastWakeTime;
  const TickType_t xFrequency = 300;
  /* 獲取當前的系統時間 */
  xLastWakeTime = xTaskGetTickCount();
  while(1)
  {
  /* 通過二值信號量實現資源互斥訪問,永久等待直到資源可用 */
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  printf("任務 vTaskMsgPro 在運行\r\n");
  bsp_LedToggle(2);
  bsp_LedToggle(3);
  xSemaphoreGive(xSemaphore);
  /* vTaskDelayUntil 是絕對延遲,vTaskDelay 是相對延遲。*/
  vTaskDelayUntil(&xLastWakeTime, xFrequency);
  }
}

   有了上面二值信號量的認識之后,互斥信號量與二值信號量又有什么區別呢?互斥信號量可以防止優先級翻轉,而二值信號量不支持,下面我們就講解一下優先級翻轉問題。

1.2 優先級翻轉問題

  下面我們通過如下的框圖來說明一下優先級翻轉的問題,讓大家有一個形象的認識。

運行條件: 

 創建 3 個任務 Task1,Task2 和 Task3,優先級分別為 3,2,1。也就是 Task1 的優先級最高。
 任務 Task1 和 Task3 互斥訪問串口打印 printf,采用二值信號實現互斥訪問。
 起初 Task3 通過二值信號量正在調用 printf,被任務 Task1 搶占,開始執行任務 Task1,也就是上圖的起始位置。

運行過程描述如下:
 任務 Task1 運行的過程需要調用函數 printf,發現任務 Task3 正在調用,任務 Task1 會被掛起,等待 Task3 釋放函數 printf。
 在調度器的作用下,任務 Task3 得到運行,Task3 運行的過程中,由於任務 Task2 就緒,搶占了 Task3的運行。優先級翻轉問題就出在這里了,從任務執行的現象上看,任務 Task1 需要等待 Task2 執行完畢才有機會得到執行,這個與搶占式調度正好反了,正常情況下應該是高優先級任務搶占低優先級任務的執行,這里成了高優先級任務 Task1 等待低優先級任務 Task2 完成。所以這種情況被稱之為優先級翻轉問題
 任務 Task2 執行完畢后,任務 Task3 恢復執行,Task3 釋放互斥資源后,任務 Task1 得到互斥資源,從而可以繼續執行。

上面就是一個產生優先級翻轉問題的現象。

1.3 FreeRTOS 互斥信號量的實現

  FreeRTOS 互斥信號量是怎么實現的呢?其實相對於二值信號量,互斥信號量就是解決了一下優先級翻轉的問題。下面我們通過如下的框圖來說明一下 FreeRTOS 互斥信號量的實現,讓大家有一個形象的認識。

運行條件:

 創建 2 個任務 Task1 和 Task2,優先級分別為 1 和 3,也就是任務 Task2 的優先級最高。
 任務 Task1 和 Task2 互斥訪問串口打印 printf。
 使用 FreeRTOS 的互斥信號量實現串口打印 printf 的互斥訪問。

運行過程描述如下:

 低優先級任務 Task1 執行過程中先獲得互斥資源 printf 的執行。此時任務 Task2 搶占了任務 Task1的執行,任務 Task1 被掛起。任務 Task2 得到執行。
 任務 Task2 執行過程中也需要調用互斥資源,但是發現任務 Task1 正在訪問,此時任務 Task1 的優先級會被提升到與 Task2 同一個優先級,也就是優先級 3,這個就是所謂的優先級繼承(Priority inheritance),這樣就有效地防止了優先級翻轉問題。任務 Task2 被掛起,任務 Task1 有新的優先級繼續執行。
 任務 Task1 執行完畢並釋放互斥資源后,優先級恢復到原來的水平。由於互斥資源可以使用,任務Task2 獲得互斥資源后開始執行。


上面就是一個簡單的 FreeRTOS 互斥信號量的實現過程。

1.4 FreeRTOS 中斷方式互斥信號量的實現

 互斥信號量僅支持用在 FreeRTOS 的任務中,中斷函數中不可使用。

 2 互 斥 信 號 量 API 函 數

使用如下 18 個函數可以實現 FreeRTOS 的信號量(含計數信號量,二值信號量和互斥信號):
 xSemaphoreCreateBinary()
 xSemaphoreCreateBinaryStatic()
 vSemaphoreCreateBinary()
 xSemaphoreCreateCounting()
 xSemaphoreCreateCountingStatic()
 xSemaphoreCreateMutex()
 xSemaphoreCreateMutexStatic()
 xSem'CreateRecursiveMutex()
 xSem'CreateRecursiveMutexStatic()
 vSemaphoreDelete()
 xSemaphoreGetMutexHolder()
 uxSemaphoreGetCount()
 xSemaphoreTake()
 xSemaphoreTakeFromISR()
 xSemaphoreTakeRecursive()
 xSemaphoreGive()
 xSemaphoreGiveRecursive()

  xSemaphoreGiveFromISR()

關於這 18 個函數的講解及其使用方法可以看 FreeRTOS 在線版手冊:

2.1 函數 xSemaphoreCreateMutex

函數原型:
SemaphoreHandle_t xSemaphoreCreateMutex( void )
函數描述:
函數 xSemaphoreCreateMutex 用於創建互斥信號量。
 返回值,如果創建成功會返回互斥信號量的句柄,如果由於 FreeRTOSConfig.h 文件中 heap 大小不足,無法為此互斥信號量提供所需的空間會返回 NULL。 

使用這個函數要注意以下問題:
1. 此函數是基於函數 xQueueCreateMutex 實現的:
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
函數 xQueueCreateMutex 的實現是基於消息隊列函數 xQueueGenericCreate 實現的。
2. 使用此函數要在 FreeRTOSConfig.h 文件中使能宏定義:
#define configUSE_MUTEXES 1

使用舉例:

static SemaphoreHandle_t xMutex = NULL;
/*
*********************************************************************************************************
* 函 數 名: AppObjCreate
* 功能說明: 創建任務通信機制
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
static void AppObjCreate (void)
{
/* 創建互斥信號量 */
xMutex = xSemaphoreCreateMutex();
if(xSemaphore == NULL)
{
/* 沒有創建成功,用戶可以在這里加入創建失敗的處理機制 */
}
}

2.2 函數 xSemaphoreGive

函數原型:
xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信號量句柄 */
函數描述:
函數 xSemaphoreGive 用於在任務代碼中釋放信號量。
 第 1 個參數是信號量句柄。
 返回值,如果信號量釋放成功返回 pdTRUE,否則返回 pdFALSE,因為信號量的實現是基於消息隊列,返回失敗的主要原因是消息隊列已經滿了。
使用這個函數要注意以下問題:
1. 此函數是基於消息隊列函數 xQueueGenericSend 實現的:
#define xSemaphoreGive( xSemaphore )  xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
2. 此函數是用於任務代碼中調用的,故不可以在中斷服務程序中調用此函數,中斷服務程序中使用的是xSemaphoreGiveFromISR。

3. 使用此函數前,一定要保證用函數 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者 xSemaphoreCreateCounting()創建了信號量。
4. 此函數不支持使用 xSemaphoreCreateRecursiveMutex()創建的信號量。

 

2.3 函數 xSemaphoreTake

函數原型:
xSemaphoreTake( SemaphoreHandle_t xSemaphore, /* 信號量句柄 */
TickType_t xTicksToWait ); /* 等待信號量可用的最大等待時間 */
函數描述:
函數 xSemaphoreTake 用於在任務代碼中獲取信號量。
 第 1 個參數是信號量句柄。
 第 2 個參數是沒有信號量可用時,等待信號量可用的最大等待時間,單位系統時鍾節拍。

 返回值,如果創建成功會獲取信號量返回 pdTRUE,否則返回 pdFALSE。
使用這個函數要注意以下問題:
1. 此函數是用於任務代碼中調用的,故不可以在中斷服務程序中調用此函數,中斷服務程序使用的是xSemaphoreTakeFromISR。
2. 如果消息隊列為空且第 2 個參數為 0,那么此函數會立即返回。
3. 如果用戶將 FreeRTOSConfig.h 文件中的宏定義 INCLUDE_vTaskSuspend 配置為 1 且第 2 個參數配置為 portMAX_DELAY,那么此函數會永久等待直到信號量可用。

使用舉例:

static SemaphoreHandle_t xMutex = NULL;
/*
*********************************************************************************************************
* 函 數 名: vTaskLED
* 功能說明: 實現串口的互斥訪問,防止多個任務同時訪問造成串口打印亂碼
* 形 參: pvParameters 是在創建該任務時傳遞的形參
* 返 回 值: 無
* 優 先 級: 2
*********************************************************************************************************
*/
static void vTaskLED(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 200;
/* 獲取當前的系統時間 */
xLastWakeTime = xTaskGetTickCount();
while(1)
{
/* 互斥信號量,xSemaphoreTake 和 xSemaphoreGive 一定要成對的調用 */
xSemaphoreTake(xMutex, portMAX_DELAY);
printf("任務 vTaskLED 在運行\r\n");
bsp_LedToggle(2);
bsp_LedToggle(3);
xSemaphoreGive(xMutex);
/* vTaskDelayUntil 是絕對延遲,vTaskDelay 是相對延遲。*/
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}

 互斥信號量,xSemaphoreTake 和 xSemaphoreGive 一定要成對的調用 

經過測試,互斥信號量是可以被其他任務釋放的,但是我們最好不要這么做,因為官方推薦的就是在同一個任務中接收和釋放。如果在其他任務釋放,不僅僅會讓代碼整體邏輯變得復雜,還會給使用和維護這套API的人帶來困難。遵守規范,總是好的。

裸機編程的時候,我經常想一個問題,就是怎么做到當一個標志位觸發的時候,立即執行某個操作,如同實現標志中斷一樣,在os編程之后,我們就可以讓一個優先級最高任務一直等待某個信號量,如果獲得信號量,就執行某個操作,實現類似標志位中斷的作用(當然,要想正真做到中斷效果,那就需要屏蔽所有可屏蔽中斷,而臨界區就可以做到)。

再說一下遞歸互斥信號量:遞歸互斥信號量,其實就是互斥信號量里面嵌套互斥信號量

使用舉例:

static void vTaskMsgPro(void *pvParameters)
{
    TickType_t xLastWakeTime;
    const TickType_t xFrequency = 1500;

    /* 獲取當前的系統時間 */
    xLastWakeTime = xTaskGetTickCount();
    
    while(1)
    {
        /* 遞歸互斥信號量,其實就是互斥信號量里面嵌套互斥信號量 */
        xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
        {
            /* -------------------------------------- */
               //假如這里是被保護的資源,第1層被保護的資源,用戶可以在這里添加被保護資源
            /* ---------------------------------------------------------------------------- */
            printf("任務vTaskMsgPro在運行,第1層被保護的資源,用戶可以在這里添加被保護資源\r\n");
            
            /* 第1層被保護的資源里面嵌套被保護的資源 */
            xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
            {
                /* ------------------------------------------------------------------------ */
                //假如這里是被保護的資源,第2層被保護的資源,用戶可以在這里添加被保護資源
                /* ------------------------------------------------------------------------ */
                printf("任務vTaskMsgPro在運行,第2層被保護的資源,用戶可以在這里添加被保護資源\r\n");
                
                /* 第2層被保護的資源里面嵌套被保護的資源 */
                xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
                {
                    printf("任務vTaskMsgPro在運行,第3層被保護的資源,用戶可以在這里添加被保護資源\r\n");
                    bsp_LedToggle(1);
                    bsp_LedToggle(4);
                }
                xSemaphoreGiveRecursive(xRecursiveMutex);
            }
            xSemaphoreGiveRecursive(xRecursiveMutex);    
        }
        xSemaphoreGiveRecursive(xRecursiveMutex);
        
        /* vTaskDelayUntil是絕對延遲,vTaskDelay是相對延遲。*/
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
    }
}

可以前面的那個官方文檔那樣用if判斷傳遞共享量:

也可以用我們遞歸互斥信號量里面的portMAX_DELAY指定永久等待,即xSemaphoreTakeRecursive返回的不是pdTRUE時,會一直等待信號量,直到有信號量來才執行后面的語句。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM