首頁 > 軟體

FreeRTOS號誌API函數基礎教學

2022-04-08 16:00:27

前言

FreeRTOS的號誌包括二進位制號誌、計數號誌、互斥號誌(以後簡稱互斥量)和遞迴互斥號誌(以後簡稱遞迴互斥量)。我們可以把互斥量和遞迴互斥量看成特殊的號誌。

號誌API函數實際上都是宏,它使用現有的佇列機制。這些宏定義在semphr.h檔案中。如果使用號誌或者互斥量,需要包含semphr.h標頭檔案。

二進位制號誌、計數號誌和互斥量號誌的建立API函數是獨立的,但是獲取和釋放API函數都是相同的;遞迴互斥號誌的建立、獲取和釋放API函數都是獨立的。

1建立二進位制號誌

1.1函數描述

SemaphoreHandle_t xSemaphoreCreateBinary( void );

這個函數用於建立一個二進位制號誌。二進位制號誌要麼有效要麼無效,這也是為什麼叫做二進位制的原因。

新建立的號誌處於無效狀態,這意味著使用API函數xSemaphoreTake()獲取訊號之前,需要先給出訊號。

二進位制號誌和互斥量非常相似,但也有細微的區別:互斥量具有優先順序繼承機制,二進位制號誌沒有這個機制。這使得二進位制號誌更適合用於同步(任務之間或者任務和中斷之間),互斥量更適合互鎖。

一旦獲得二進位制號誌後不需要恢復,一個任務或中斷不斷的產生訊號,而另一個任務不斷的取走這個訊號,通過這樣的方式來實現同步。

低優先順序任務擁有互斥量的時候,如果另一個高優先順序任務也企圖獲取這個號誌,則低優先順序任務的優先順序會被臨時提高,提高到和高優先順序任務相同的優先順序。這意味著互斥量必須要釋放,否則高優先順序任務將不能獲取這個互斥量,並且那個擁有互斥量的低優先順序任務也永遠不會被剝奪,這就是作業系統中的優先順序翻轉。

互斥量和二進位制號誌都是SemaphoreHandle_t型別,並且可以用於任何具有這類引數的API函數中。

1.2返回值

NULL:建立號誌失敗,因為FreeRTOS堆疊不足。其它值:號誌建立成功。這個返回值儲存著號誌控制程式碼。

1.3用法舉例

SemaphoreHandle_t xSemaphore;
void vATask( void * pvParameters )
{
    /* 建立號誌 */
   xSemaphore = xSemaphoreCreateBinary();
   if( xSemaphore == NULL )
   {
       /* 因堆疊不足,號誌建立失敗,這裡進行失敗處理*/
   }
   else
   {
       /* 號誌可以使用。號誌控制程式碼儲存在變數xSemahore中。
          如果在這裡呼叫API函數xSemahoreTake()來獲取號誌,
          則必然是失敗的,因為建立的號誌初始是無效(空)的。*/
   }
}

2建立計數號誌

2.1函數描述

SemaphoreHandle_t xSemaphoreCreateCounting ( UBaseType_t uxMaxCount,
                                 UBaseType_t uxInitialCount )

建立計數號誌,計數號誌通常用於以下兩種情況:

事件計數:在這種應用場合,每當事件發生,事件處理程式會“產生”一個號誌(號誌計數值會遞增),每當處理任務處理事件,會取走一個號誌(號誌計數值會遞減)。因此,事件發生或者事件被處理後,計數值是會變化的。

資源管理:在這種應用場合下,計數值表示有效資源的數目。為了獲得資源,任務首先要獲得一個號誌---遞減號誌計數值。當計數值為0時,表示沒有可用的資源。當佔有資源的任務完成,它會釋放這個資源,相應的號誌計數值會增一。計數值達到初始值(最大值)表示所有資源都可用。

2.2引數描述

uxMaxCount:最大計數值,當訊號到達這個值後,就不再增長了。

uxInitialCount:建立號誌時的初始值。

2.3返回值

NULL表示號誌建立失敗,否則返回號誌控制程式碼。

2.4用法舉例

void vATask( void * pvParameters )
 {
     xSemaphoreHandle xSemaphore;
     // 必須先建立號誌,才能使用它
     // 號誌可以計數的最大值為10,計數初始值為0.
     xSemaphore = xSemaphoreCreateCounting( 10, 0 );
     if( xSemaphore != NULL )
     {
         // 號誌建立成功
         // 現在可以使用號誌了。
     }
 }

3建立互斥量

3.1函數描述

SemaphoreHandle_t xSemaphoreCreateMutex( void )

建立互斥量。

可以使用API函數xSemaphoreTake()和xSemaphoreGive()存取互斥量,但是絕不可以用xSemaphoreTakeRecursive()和xSemaphoreGiveRecursive()存取。

二進位制號誌和互斥量非常相似,但也有細微的區別:互斥量具有優先順序繼承機制,二進位制號誌沒有這個機制。這使得二進位制號誌更適合用於同步(任務之間或者任務和中斷之間),互斥量更適合互鎖。

一旦獲得二進位制號誌後不需要恢復,一個任務或中斷不斷的產生訊號,而另一個任務不斷的取走這個訊號,通過這樣的方式來實現同步。

低優先順序任務擁有互斥量的時候,如果另一個高優先順序任務也企圖獲取這個號誌,則低優先順序任務的優先順序會被臨時提高,提高到和高優先順序任務相同的優先順序。這意味著互斥量必須要釋放,否則高優先順序任務將不能獲取這個互斥量,並且那個擁有互斥量的低優先順序任務也永遠不會被剝奪,這就是作業系統中的優先順序翻轉。

互斥量和二進位制號誌都是SemaphoreHandle_t型別,並且可以用於任何具有這類引數的API函數中。

3.2返回值

NULL表示號誌建立失敗,否則返回號誌控制程式碼。

3.3用法舉例

xSemaphoreHandle xSemaphore;
voidvATask( void * pvParameters )
{
    // 互斥量在未建立之前是不可用的
    xSemaphore = xSemaphoreCreateMutex();
    if( xSemaphore != NULL )
    {
        // 建立成功
        // 在這裡可以使用這個互斥量了 
    }
}

4建立遞迴互斥量

4.1函數描述

SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )

用於建立遞迴互斥量。被建立的互斥量可以被API函數xSemaphoreTakeRecursive()和xSemaphoreGiveRecursive()使用,但不可以被API函數xSemaphoreTake()和xSemaphoreGive()使用。

遞迴型別的互斥量可以被擁有者重複獲取。擁有互斥量的任務必須呼叫API函數xSemaphoreGiveRecursive()將擁有的遞迴互斥量全部釋放後,該號誌才真正被釋放。比如,一個任務成功獲取同一個互斥量5次,那麼這個任務要將這個互斥量釋放5次之後,其它任務才能獲取到它。

遞迴互斥量具有優先順序繼承機制,因此任務獲得一次訊號後必須在使用完後做一個釋放操作。

互斥量型別訊號不可以用在中斷服務例程中。

4.2返回值

NULL表示互斥量建立失敗,否則返回互斥量控制程式碼。

4.3用法舉例

xSemaphoreHandle xMutex;
void vATask( void * pvParameters )
{
    // 互斥量未建立前是不能被使用的
    xMutex = xSemaphoreCreateRecursiveMutex();
    if( xMutex != NULL )
    {
        // 建立成功
        // 在這裡建立互斥量
    }
}

5刪除號誌

5.1函數描述

void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

刪除號誌。如果有任務阻塞在這個號誌上,則這個號誌不要刪除。

5.2引數描述

xSemaphore:號誌控制程式碼

6獲取號誌

6.1函數描述

xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)

獲取號誌。號誌必須是通過API函數xSemaphoreCreateBinary()、xSemaphoreCreateCounting()和xSemaphoreCreateMutex()預先建立過的。注意,遞迴互斥量型別號誌不能使用該函數、不用在中斷服務程式中使用該函數。

6.2引數描述

xSemaphore:號誌控制程式碼

xTickToWait:號誌無效時,任務最多等待的時間,單位是系統節拍週期個數。使用宏portTICK_PERIOD_MS可以輔助將系統節拍個數轉化為實際時間(以毫秒為單位)。如果設定為0,表示不是設定等待時間。如果INCLUDE_vTaskSuspend設定為1,並且引數xTickToWait為portMAX_DELAY則可以無限等待。

6.3返回值

成功獲取到號誌返回pdTRUE,否則返回pdFALSE。

6.4用法舉例

SemaphoreHandle_t xSemaphore = NULL;
/*這個任務建立號誌 */
void vATask( void * pvParameters )
{
    /*建立互斥型號誌,用於保護共用資源。*/
    xSemaphore = xSemaphoreCreateMutex();
}
/* 這個任務使用號誌 */
void vAnotherTask( void * pvParameters )
{
    /* ... 做其它事情. */
    if( xSemaphore != NULL )
    {
        /*如果號誌無效,則最多等待10個系統節拍週期。*/
        if( xSemaphoreTake( xSemaphore, ( TickType_t ) 10 ) == pdTRUE )
        {
            /*到這裡我們獲取到號誌,現在可以存取共用資源了*/
            /* ... */
            /* 完成存取共用資源後,必須釋放號誌*/
            xSemaphoreGive( xSemaphore );
        }
        else
        {
            /* 沒有獲取到號誌,這裡處理異常情況。*/
        }
    }
}

7獲取號誌(帶中斷保護)

7.1函數描述

xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
signedBaseType_t *pxHigherPriorityTaskWoken)

API函數xSemaphoreTake()的另一版本,用於中斷服務程式。

7.2引數描述

xSemaphore:號誌控制程式碼

pxHigherPriorityTaskWoken:如果*pxHigherPriorityTaskWoken為pdTRUE,則需要在中斷退出前手動進行一次上下文切換。從FreeRTOS V7.3.0開始,該引數為可選引數,並可以設定為NULL。

7.3返回值

號誌成功獲取返回pdTRUE,否則返回pdFALSE。

8獲取遞迴互斥量

8.1函數描述

xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex, TickType_t xTicksToWait );

獲取遞迴互斥號誌。互斥量必須是通過API函數xSemaphoreCreateRecursiveMutex()建立的型別。

檔案FreeRTOSConfig.h中的宏configUSE_RECURSIVE_MUTEXES必須設定成1,此函數才有效。

已經獲取遞迴互斥量的任務可以重複獲取該遞迴互斥量。

使用xSemaphoreTakeRecursive()函數成功獲取幾次遞迴互斥量,就要使用xSemaphoreGiveRecursive()函數返還幾次,在此之前遞迴互斥量都處於無效狀態。比如,某個任務成功獲取5次遞迴互斥量,那麼在它沒有返還5次該遞迴互斥量之前,這個互斥量對別的任務無效。

8.2引數描述

xMutex:互斥量控制程式碼,必須是使用API函數xSemaphoreCreateRecursiveMutex()返回的。

xTickToWait:互斥量無效時,任務最多等待的時間,單位是系統節拍週期個數。使用宏portTICK_PERIOD_MS可以輔助將系統節拍個數轉化為實際時間(以毫秒為單位)。如果設定為0,表示不是設定等待時間。如果任務已經擁有號誌則xSemaphoreTakeRecursive()立即返回,不管xTickToWait是什麼值。

8.3返回值

成功獲取遞迴互斥量返回pdTURE,否則返回pdFALSE。

8.4用法舉例

SemaphoreHandle_t xMutex = NULL;
// 這個任務建立互斥量
void vATask( void *pvParameters )
{
    // 這個互斥量用於保護共用資源
    xMutex =xSemaphoreCreateRecursiveMutex();
}
//這個任務使用互斥量
void vAnotherTask( void *pvParameters )
{
    // ... 做其它事情.
    if( xMutex != NULL )
    {
        // 如果互斥量無效,則最多等待10系統時鐘節拍週期.   
        if(xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ) == pdTRUE )
        {
            // 到這裡我們成功獲取互斥量並可以存取共用資源了
            // ...
            // 由於某種原因,某些程式碼需要在一個任務中多次呼叫API函數
            // xSemaphoreTakeRecursive()。當然不會像本例中這樣連續式
            //呼叫,實際程式碼會有更加複雜的結構
            xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
            xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
            // 我們獲取一個互斥量三次,所以我們要將這個互斥量釋放三次
            //它才會變得有效。再一次說明,實際程式碼可能會更加複雜。
            xSemaphoreGiveRecursive( xMutex );
            xSemaphoreGiveRecursive( xMutex );
            xSemaphoreGiveRecursive( xMutex );
            // 到這裡,這個共用資源可以被其它任務使用了.
        }
        else
        {
            // 處理異常情況
        }
    }
}

9釋放號誌

9.1函數描述

xSemaphoreGive(SemaphoreHandle_t xSemaphore )

用於釋放一個號誌。號誌必須是API函數xSemaphoreCreateBinary()、xSemaphoreCreateCounting()或xSemaphoreCreateMutex() 建立的。必須使用API函數xSemaphoreTake()獲取這個號誌。

這個函數絕不可以在中斷服務例程中使用,可以使用帶中斷保護版本的API函數xSemaphoreGiveFromISR()來實現相同功能。

這個函數不能用於使用API函數xSemaphoreCreateRecursiveMutex()所建立的遞迴互斥量。

9.2引數描述

xSemaphore:號誌控制程式碼。

9.3返回值

號誌釋放成功返回pdTRUE,否則返回pdFALSE。

9.4用法舉例

SemaphoreHandle_t xSemaphore = NULL;
voidvATask( void * pvParameters )
{
   // 建立一個互斥量,用來保護共用資源
   xSemaphore = xSemaphoreCreateMutex();
 
    if( xSemaphore != NULL )
    {
         if( xSemaphoreGive( xSemaphore ) != pdTRUE )
         {
              //我們希望這個函數呼叫失敗,因為首先要獲取互斥量
         }
         // 獲取號誌,不等待
         if( xSemaphoreTake( xSemaphore, ( TickType_t ) 0 ) )
         {
              // 現在我們擁有互斥量,可以安全的存取共用資源
              if( xSemaphoreGive( xSemaphore ) != pdTRUE )
              {
                   //我們不希望這個函數呼叫失敗,因為我們必須
                   //要釋放已獲取的互斥量
              }
         }
     }
}

10釋放號誌(帶中斷保護)

10.1函數描述

xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,
signed BaseType_t *pxHigherPriorityTaskWoken )

釋放號誌。是API函數xSemaphoreGive()的另個版本,用於中斷服務程式。號誌必須是通過API函數xSemaphoreCreateBinary()或xSemaphoreCreateCounting()建立的。這裡沒有互斥量,是因為互斥量不可以用在中斷服務程式中。

10.2引數描述

xSemaphore:號誌控制程式碼

pxHigherPriorityTaskWoken:如果*pxHigherPriorityTaskWoken為pdTRUE,則需要在中斷退出前人為的經行一次上下文切換。從FreeRTOS V7.3.0開始,該引數為可選引數,並可以設定為NULL。

10.3返回值

成功釋放號誌返回pdTURE,否則返回errQUEUE_FULL。

10.4用法舉例

#define LONG_TIME 0xffff
#define TICKS_TO_WAIT    10
SemaphoreHandle_t xSemaphore = NULL;
/* Repetitive task. */
void vATask( void * pvParameters )
{
    /* 我們使用號誌同步,所以先建立一個二進位制號誌.必須確保
       在建立這個二進位制號誌之前,中斷不會存取它。*/
    xSemaphore = xSemaphoreCreateBinary();
 
    for( ;; )
    {
        /* 我們希望每產生10次定時器中斷,任務執行一次。*/
        if( xSemaphoreTake( xSemaphore, LONG_TIME ) == pdTRUE )
        {
            /* 到這裡成功獲取到號誌*/
            ...
           /* 我們成功執行完一次,由於這是個死迴圈,所以任務仍會
               阻塞在等待號誌上。號誌由ISR釋放。*/
        }
    }
}
/* 定時器 ISR */
void vTimerISR( void * pvParameters )
{
    static unsigned char ucLocalTickCount = 0;
    static signed BaseType_txHigherPriorityTaskWoken;
    /*定時器中斷髮生 */
      ...執行其它程式碼
    /*需要vATask() 執行嗎? */
    xHigherPriorityTaskWoken = pdFALSE;
    ucLocalTickCount++;
    if( ucLocalTickCount >= TICKS_TO_WAIT )
    {
        /* 釋放號誌,解除vATask任務阻塞狀態 */
        xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
        /* 復位計數器 */
        ucLocalTickCount = 0;
    }
    /* 如果 xHigherPriorityTaskWoken 表示式為真,需要執行一次上下文切換*/
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

11釋放遞迴互斥量

11.1函數描述

xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex )

釋放一個遞迴互斥量。互斥量必須是使用 API函數xSemaphoreCreateRecursiveMutex()建立的。檔案FreeRTOSConfig.h中宏configUSE_RECURSIVE_MUTEXES必須設定成1本函數才有效。

11.2引數描述

xMutex:互斥量控制程式碼。必須是函數xSemaphoreCreateRecursiveMutex()返回的值。

11.3返回值

如果遞迴互斥量釋放成功,返回pdTRUE。

11.4用法舉例

見“8 獲取遞迴互斥量”。

12獲取互斥量持有任務的控制程式碼

12.1函數描述

TaskHandle_t xSemaphoreGetMutexHolder( SemaphoreHandle_t xMutex );

返回互斥量持有任務的控制程式碼(如果有的話),互斥量由引數xMutex指定。

如果呼叫此函數的任務持有互斥量,那麼可以可靠的返回任務控制程式碼,但是如果是別的任務持有互斥量,則不總可靠。

檔案FreeRTOSConfig.h中宏configUSE_MUTEXES必須設定成1本函數才有效。

12.2引數描述

xMutex:互斥量控制程式碼

12.3返回值

返回互斥量持有任務的控制程式碼。如果引數xMutex不是互斥型別號誌或者雖然互斥量有效但這個互斥量不被任何任務持有則返回NULL。

這是FreeRTOS基礎篇的最後一篇博文,到這裡我們已經可以移植、熟練使用FreeRTOS了。但如果想知道FreeRTOS背後的執行機制,這些是遠遠不夠的,下面要走的路還會很長。要不要了解FreeRTOS背後執行機制,全憑各位的興趣,畢竟我們即使不清楚汽車的構造細節,但只要掌握駕駛技巧也可以很好的開車的。使用RTOS也與之相似,只要我們掌握了基礎篇的那些知識,我們已經可以很好的使用FreeRTOS了。探索FreeRTOS背後執行的機制,可以讓我們更優雅、更少犯錯、更舉重若輕的的使用RTOS。

FreeRTOS高階篇已經開始寫了,更多關於FreeRTOS號誌API函數的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com