ThreadX——IPC應用之訊號量
阿新 • • 發佈:2020-12-14
# 一、應用簡介
在RTOS的應用開發中,`訊號量`也是經常使用到的一種用於多工之間資訊同步、資源互斥訪問的一種手段,常用於協調多個任務訪問同一資源的場景。訊號量又分為`計數訊號量`和`互斥訊號量`。計數訊號量可以表示整體資源數量,當獲取一個資源後計數訊號量減一,釋放一個資源後計數訊號量加一,當訊號量為0時即表明資源被全部分配導致無法再獲取資源,任務可以掛起等待直到有資源可用。互斥訊號量是可以理解為只能維護資源數量為1的二值計數訊號量(值為0或1),但是互斥訊號量又不同於計數訊號量,因為它還具有`優先順序繼承`的機制(優先順序繼承機制是RTOS中為了避免出現`優先順序翻轉`問題而做的處理方式。簡單來說就是如果低優先順序持有互斥訊號量那麼高優先順序任務想訪問互斥量就會失敗而掛起等待互斥量被釋放,此時反而是低優先順序任務在執行,這就出現了優先順序翻轉。為了避免該情況RTOS處理方式是把正在持有互斥量執行的低優先順序任務的優先順序提高到與等待訪問互斥資源的高優先順序任務同等優先順序,這就是優先順序繼承。等互斥量被釋放後RTOS會將該任務恢復到之前的低優先順序)。
訊號量應用有一個很貼切的舉例就是停車場管理。可以把停車場的所有停車位看作計數訊號量初始值,當有車輛進入停車場計數訊號量值減一,當有車輛離開計數訊號量值加一。當值為0時說明沒有空車位了,外面車輛無法再停進來,需要等到有空車位出現(計數訊號量非0)。停車場中的每一個停車位又可以用互斥訊號量來表示,當有車輛佔據某車位時表明該車輛持有該車位的互斥訊號量,其他車輛無法再停在該車位(如果有人非要槓說萬一車位大可以停2個小車那我莫法哈哈哈)。當該車位車輛離開後表明釋放該車位的互斥訊號量,此時其他車輛可以停入該車位。
# 二、API簡介
> **下面列出ThreadX中使用訊號量常用到的函式**
## 1、建立計數訊號量
> - 描述
>
> 此服務為執行緒間同步建立一個計數訊號量。初始訊號量計數被指定為輸入引數
>
> - 引數
>
> - **semaphore_ptr** 指向訊號量控制塊的指標
> - **name_ptr** 指向訊號量名稱的指標
> - **initial_count** 指定此訊號量的初始計數。 合法值的範圍是0x00000000至0xFFFFFFFF
>
> - 返回值
>
> - **TX_SUCCESS** (0x00) 成功建立訊號量
> - **TX_SEMAPHORE_ERROR** (0x0C) 無效的訊號量指標。指標是 NULL 或者已經建立了訊號量
> - **TX_CALLER_ERROR** (0x13) 該服務的呼叫者無效
```c
UINT tx_semaphore_create(
TX_SEMAPHORE *semaphore_ptr,
CHAR *name_ptr,
ULONG initial_count);
```
## 2、刪除計數訊號量
> - 描述
>
> - 此服務刪除指定的計數訊號量,恢復所有掛起的等待訊號量的執行緒,並返回TX_DELETED狀態。
>
> - 在刪除訊號量之前,應用程式必須確保完成(或禁用)此訊號量的 put_notify 回撥。此外,應用程式必須防止再使用已刪除的訊號量。
>
> - 引數
>
> - **semaphore_ptr** 指向先前建立的訊號量的指標
>
> - 返回值
>
> - **TX_SUCCESS** (0x00) 成功刪除計數訊號量
> - **TX_SEMAPHORE_ERROR** (0x0C) 無效的計數訊號量指標
> - **TX_CALLER_ERROR** (0x13) 該服務的呼叫者無效
```c
UINT tx_semaphore_delete(TX_SEMAPHORE *semaphore_ptr);
```
## 3、獲取計數訊號量
> - 描述
>
> 此服務從指定的計數訊號量獲取一個訊號量,指定的訊號量計數減少一個。
>
> - 引數
>
> - **semaphore_ptr** 指向先前建立的計數訊號量的指標
> - **wait_option** :**TX_NO_WAIT** (0x00000000)不等待直接返回獲取結果;**TX_WAIT_FOREVER** (0xFFFFFFFF)一直掛起等待直到獲取到訊號量;**0x00000001 ~ 0xFFFFFFFE**指定掛起等待多少個心跳節拍。
>
> - 返回值
>
> - **TX_SUCCESS** (0x00) 成功獲取訊號量
> - **TX_DELETED** (0x01) 執行緒掛起時刪除了計數訊號量
> - **TX_NO_INSTANCE** (0x0D) 服務無法檢索計數訊號量的例項(在指定的等待時間內訊號量計數為零)
> - **TX_WAIT_ABORTED** (0x1A) 被其他執行緒或計時器或中斷打斷而導致服務掛起
> - **TX_SEMAPHORE_ERROR** (0x0C) 計數訊號量指標無效
> - **TX_WAIT_ERROR** (0x04) 在非執行緒呼叫中指定了TX_NO_WAIT以外的等待選項
```c
UINT tx_semaphore_get(
TX_SEMAPHORE *semaphore_ptr,
ULONG wait_option);
```
## 4、獲取計數訊號量資訊
> - 描述
>
> 該服務檢索有關指定訊號量的資訊
>
> - 引數
>
> 引數為TX_NULL表示不獲取該引數的資訊
>
> - **semaphore_ptr** 指向訊號量控制塊的指標
> - **name** 指向訊號量名稱的指標的目標指標
> - **current_value** 指向當前訊號量計數的目標的指標
> - **first_suspended** 指向這個訊號量掛起列表中第一個執行緒的指標
> - **suspended_count** 指向當前掛起在此訊號量上的執行緒數的指標
> - **next_semaphore** 指向下一個建立的訊號量指標的目標指標
>
> - 返回值
>
> - **TX_SUCCESS** (0x00) 獲取資訊成功
> - **TX_SEMAPHORE_ERROR** (0x0C) 無效的訊號量指標
```c
UINT tx_semaphore_info_get(
TX_SEMAPHORE *semaphore_ptr,
CHAR **name, ULONG *current_value,
TX_THREAD **first_suspended,
ULONG *suspended_count,
TX_SEMAPHORE **next_semaphore);
```
# 5、增加計數訊號量
> - 描述
> - 該服務將指定的訊號量計數加一。
> - 如果在訊號量為0xFFFFFFFF時呼叫此服務,則新的put操作將導致訊號量重置為零。
> - 引數
> - **semaphore_ptr** 指向建立的計數訊號量控制塊的指標
> - 返回值
> - **TX_SUCCESS** (0x00) 成功放置訊號量
> - **TX_SEMAPHORE_ERROR** (0x0C) 指向訊號量的指標無效
```c
UINT tx_semaphore_put(TX_SEMAPHORE *semaphore_ptr);
```
# 6、增加指定上限的計數訊號量
> - 描述
>
> 該服務將指定的計數訊號量增加一個。 如果計數訊號量的當前值大於或等於指定的上限,則不會增加訊號量,並且將返回TX_CEILING_EXCEEDED錯誤。
>
> - 引數
>
> - **semaphore_ptr** 指向先前建立的訊號量的指標
> - **ceiling** 訊號量所允許的上限值(有效值範圍是1到0xFFFFFFFF)
>
> - 返回值
>
> - **TX_SUCCESS (0x00)** 操作成功
> - **TX_CEILING_EXCEEDED** (0x21) Put請求超過上限
> - **TX_INVALID_CEILING** (0x22) 為上限提供了無效值零
> - **TX_SEMAPHORE_ERROR** (0x0C) 無效的訊號量指標
```c
UINT tx_semaphore_ceiling_put(
TX_SEMAPHORE *semaphore_ptr,
ULONG ceiling);
```
## 7、建立互斥訊號量
> - 描述
>
> 此服務為執行緒間互斥建立互斥體以保護資源。
>
> - 引數
>
> - **mutex_ptr** 指向互斥量控制塊的指標
> - **name_ptr** 指向互斥量名稱的指標
> - **priority_inherit** 指定此互斥物件是否支援優先順序繼承。 如果此值為TX_INHERIT,則支援優先順序繼承。 如果指定TX_NO_INHERIT,則此互斥鎖不支援優先順序繼承。
>
> - 返回值
>
> - **TX_SUCCESS** (0x00) 成功建立訊號量
> - **TX_MUTEX_ERROR** (0x1C)無效的互斥指標。 指標為NULL或互斥體已建立
> - **TX_CALLER_ERROR** (0x13) 該服務的呼叫者無效
> - **TX_INHERIT_ERROR** (0x1F) 無效的優先順序繼承引數
```c
UINT tx_mutex_create(
TX_MUTEX *mutex_ptr,
CHAR *name_ptr,
UINT priority_inherit);
```
# 8、刪除互斥訊號量
> - 描述
>
> - 該服務將刪除指定的互斥訊號量。 恢復所有等待互斥的已暫停執行緒,並返回TX_DELETED返回狀態。
>
> - 應用程式應防止使用已刪除的互斥訊號量
>
> - 引數
>
> - **mutex_ptr** 指向先前建立的互斥體的指標
>
> - 返回值
>
> - **TX_SUCCESS** (0x00) 操作成功
> - **TX_MUTEX_ERROR** (0x1C) 無效的互斥體指標
> - **TX_CALLER_ERROR** (0x13) 該服務的呼叫者無效
```c
UINT tx_mutex_delete(TX_MUTEX *mutex_ptr);
```
# 9、獲取互斥訊號量
> - 描述
> - 該服務嘗試獲取指定互斥鎖的獨佔所有權。 如果呼叫執行緒已經擁有互斥鎖,則內部計數器將遞增,並返回成功狀態
> - 如果互斥鎖由另一個執行緒擁有,並且該執行緒具有更高的優先順序,並且在互斥鎖建立時指定了優先順序繼承,則優先順序較低的執行緒的優先順序將暫時提高到呼叫執行緒的優先順序。
> - 擁有互斥量的低優先順序執行緒的優先順序在互斥體所有權期間絕對不能由外部執行緒修改
> - 引數
> - **mutex_ptr** 指向先前建立的互斥體的指標
> - **wait_option**:**TX_NO_WAIT** (0x00000000)不等待直接返回獲取結果;**TX_WAIT_FOREVER** (0xFFFFFFFF)一直掛起等待直到獲取到訊號量;**0x00000001 ~ 0xFFFFFFFE**指定掛起等待多少個心跳節拍。
> - 返回值
> - **TX_SUCCESS** (0x00) 操作成功
> - **TX_DELETED** (0x01) 執行緒暫停時互斥體被刪除
> - **TX_NOT_AVAILABLE** (0x1D) 服務無法在指定的等待時間內獲得互斥鎖的所有權
> - **TX_WAIT_ABORTED** (0x1A) 被其他執行緒或計時器或中斷打斷而導致服務掛起
> - **TX_MUTEX_ERROR** (0x1C) 無效的互斥體指標
> - **TX_WAIT_ERROR** (0x04) 在非執行緒呼叫中指定了TX_NO_WAIT以外的等待選項
> - **TX_CALLER_ERROR** (0x13) 該服務的呼叫者無效
```c
UINT tx_mutex_get(
TX_MUTEX *mutex_ptr,
ULONG wait_option);
```
# 10、釋放互斥訊號量
> - 描述
> - 此服務將釋放互斥訊號量
> - 如果在建立互斥物件時選擇了優先順序繼承,那麼釋放執行緒的優先順序將恢復到它最初獲得互斥物件所有權時的優先順序。在擁有互斥物件期間對釋放執行緒所做的任何其他優先順序更改都可能被撤消。
> - 引數
> - **mutex_ptr** 指向先前建立的互斥體的指標
> - 返回值
> - **TX_SUCCESS** (0x00) 操作成功
> - **TX_NOT_OWNED** (0x1E) 互斥物件不歸呼叫者所有
> - **TX_MUTEX_ERROR** (0x1C) 無效的互斥體指標
> - **TX_CALLER_ERROR** (0x13) 該服務的呼叫者無效
```c
UINT tx_mutex_put(TX_MUTEX *mutex_ptr);
```
# 三、例項演示
> - 該例項用到的硬體資源:一個串列埠、兩個按鍵、一個LED
> - 建立一個計數訊號量,指定數量上限為3,KEY1申請計數訊號量,KEY2增加計數訊號量
> - 建立一個互斥訊號量,用於KEY1、KEY2互斥,即兩個按鍵不能同時按下
> - 建立2個任務,一個用於KEY1任務,一個用於KEY2任務,
```c
#define DEMO_STACK_SIZE (2 * 1024)
#define DEMO_BYTE_POOL_SIZE (32 * 1024)
TX_THREAD thread_0;
TX_THREAD thread_1;
TX_BYTE_POOL byte_pool_0;
UCHAR memory_area[DEMO_BYTE_POOL_SIZE];
TX_MUTEX tx_semaphore_mutex; // 互斥訊號量
TX_SEMAPHORE tx_semaphore_count; // 計數訊號量
```
```c
void tx_application_define(void *first_unused_memory)
{
UINT status;
CHAR *pointer = TX_NULL;
/* 建立互斥訊號量 */
status = tx_mutex_create(&tx_semaphore_mutex,"tx_semaphore_mutex",TX_NO_INHERIT);
if (TX_SUCCESS != status)
{
// 建立失敗處理
}
/* 建立計數訊號量 */
status = tx_semaphore_create(&tx_semaphore_count, "tx_semaphore_count", 3);
if (TX_SUCCESS != status)
{
// 建立失敗處理
}
/* Create a byte memory pool from which to allocate the thread stacks. */
tx_byte_pool_create(&byte_pool_0, "byte pool 0", memory_area, DEMO_BYTE_POOL_SIZE);
/* Allocate the stack for thread 0. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
/* Create the main thread. */
tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
pointer, DEMO_STACK_SIZE,
1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Allocate the stack for thread 1. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
/* Create threads 1 */
tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0,
pointer, DEMO_STACK_SIZE,
2, 2, TX_NO_TIME_SLICE, TX_AUTO_START);
}
```
```C
void thread_0_entry(ULONG thread_input)
{
UINT status, key_flag = 0;
CHAR *name;
ULONG current_value;
TX_THREAD *first_suspended;
ULONG suspended_count;
TX_SEMAPHORE *next_semaphore;
while(1)
{
if (0 == key_flag)
{
if (GPIO_PIN_SET == HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin))
{
key_flag = 1;
/*按鍵按下,獲取互斥訊號量*/
status = tx_mutex_get(&tx_semaphore_mutex, TX_NO_WAIT);
if (TX_SUCCESS == status)
{
/*獲取計數訊號量*/
if (TX_SUCCESS == tx_semaphore_get(&tx_semaphore_count, TX_NO_WAIT))
{
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
/*獲取計數訊號量資訊*/
tx_semaphore_info_get(&tx_semaphore_count,
&name,
¤t_value,
&first_suspended,
&suspended_count,
&next_semaphore);
printf("key1 pressed, current count semaphore is %d\r\n",(int)current_value);
}
else
{
printf("key1 failed to get count semaphore\r\n");
}
}
else
{
printf("key1 failed to get mutex semaphore\r\n");
}
}
}
else
{
if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin))
{
key_flag = 0;
/*按鍵鬆開,釋放互斥訊號量*/
tx_mutex_put(&tx_semaphore_mutex);
}
}
tx_thread_sleep(20);
}
}
```
```c
void thread_1_entry(ULONG thread_input)
{
UINT status, key_flag = 0;
ULONG current_value;
while(1)
{
if (0 == key_flag)
{
if (GPIO_PIN_SET == HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin))
{
key_flag = 1;
/*按鍵按下,獲取互斥訊號量*/
status = tx_mutex_get(&tx_semaphore_mutex, TX_NO_WAIT);
if (TX_SUCCESS == status)
{
/*計數訊號量加一,指定上限為3*/
//tx_semaphore_put(&tx_semaphore_count);
tx_semaphore_ceiling_put(&tx_semaphore_count,3);
/*獲取計數訊號量資訊*/
tx_semaphore_info_get(&tx_semaphore_count,
TX_NULL,
¤t_value,
TX_NULL,
TX_NULL,
TX_NULL);
printf("key2 pressed, current count semaphore is %d\r\n",(int)current_value);
}
else
{
printf("key2 failed to get mutex semaphore\r\n");
}
}
}
else
{
if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin))
{
key_flag = 0;
/*按鍵鬆開,釋放互斥訊號量*/
tx_mutex_put(&tx_semaphore_mutex);
}
}
tx_thread_sleep(20);
}
}
```
> 串列埠列印演示結果如下
```
[20:51:33.330]收←◆key1 pressed, current count semaphore is 2
[20:51:34.034]收←◆key1 pressed, current count semaphore is 1
[20:51:34.556]收←◆key1 pressed, current count semaphore is 0
[20:51:34.939]收←◆key1 failed to get count semaphore
[20:51:35.522]收←◆key1 failed to get count semaphore
[20:51:37.004]收←◆key2 pressed, current count semaphore is 1
[20:51:37.488]收←◆key2 pressed, current count semaphore is 2
[20:51:37.851]收←◆key2 pressed, current count semaphore is 3
[20:51:38.234]收←◆key2 pressed, current count semaphore is 3
[20:51:38.556]收←◆key2 pressed, current count semaphore is 3
[20:51:44.925]收←◆key1 pressed, current count semaphore is 2
[20:51:46.579]收←◆key2 failed to get mutex semaphore
[20:51:52.102]收←◆key2 pressed, current count semaphore is 3
[20:51:52.987]收←◆key1 failed to get mutex semaphore
```
![](Snipaste_2020-12-13_20-56