文章目錄
- 系列教程總目錄
- 概述
-
6.1 信號量的特性
- 6.1.1 信號量的常規操作
- 6.1.2 信號量跟隊列的對比
- 6.1.3 兩種信號量的對比
-
6.2 信號量函數
- 6.2.1 創建
- 6.2.2 刪除
- 6.2.3 give/take
- 6.3 示例12: 使用二進制信號量來同步
- 6.4 示例13: 防止數據丟失
- 6.5 示例14: 使用計數型信號量
需要獲取更好閱讀體驗的同學,請訪問我專門設立的站點查看,地址:http://rtos.100ask.net/
系列教程總目錄
本教程連載中,篇章會比較多,為方便同學們閱讀,點擊這里可以查看文章的 目錄列表,目錄列表頁面地址:https://blog.csdn.net/thisway_diy/article/details/121399484
概述
前面介紹的隊列(queue)可以用于傳輸數據:在任務之間、任務和中斷之間。
有時候我們只需要傳遞狀態,并不需要傳遞具體的信息,比如:
- 我的事做完了,通知一下你
- 賣包子了、賣包子了,做好了1個包子!做好了2個包子!做好了3個包子!
- 這個停車位我占了,你們只能等著
在這種情況下我們可以使用信號量(semaphore),它更節省內存。
本章涉及如下內容:
- 怎么創建、刪除信號量
- 怎么發送、獲得信號量
- 什么是計數型信號量?什么是二進制信號量?
6.1 信號量的特性
6.1.1 信號量的常規操作
信號量這個名字很恰當:
- 信號:起通知作用
-
量:還可以用來表示資源的數量
- 當"量"沒有限制時,它就是"計數型信號量"(Counting Semaphores)
- 當"量"只有0、1兩個取值時,它就是"二進制信號量"(Binary Semaphores)
- 支持的動作:"give"給出資源,計數值加1;"take"獲得資源,計數值減1
計數型信號量的典型場景是:
- 計數:事件產生時"give"信號量,讓計數值加1;處理事件時要先"take"信號量,就是獲得信號量,讓計數值減1。
- 資源管理:要想訪問資源需要先"take"信號量,讓計數值減1;用完資源后"give"信號量,讓計數值加1。
信號量的"give"、"take"雙方并不需要相同,可以用于生產者-消費者場合:
- 生產者為任務A、B,消費者為任務C、D
-
一開始信號量的計數值為0,如果任務C、D想獲得信號量,會有兩種結果:
- 阻塞:買不到東西咱就等等吧,可以定個鬧鐘(超時時間)
- 即刻返回失敗:不等
- 任務A、B可以生產資源,就是讓信號量的計數值增加1,并且把等待這個資源的顧客喚醒
- 喚醒誰?誰優先級高就喚醒誰,如果大家優先級一樣就喚醒等待時間最長的人
二進制信號量跟計數型的唯一差別,就是計數值的最大值被限定為1。
6.1.2 信號量跟隊列的對比
差異列表如下:
隊列 | 信號量 |
---|---|
可以容納多個數據, 創建隊列時有2部分內存: 隊列結構體、存儲數據的空間 |
只有計數值,無法容納其他數據。 創建信號量時,只需要分配信號量結構體 |
生產者:沒有空間存入數據時可以阻塞 | 生產者:用于不阻塞,計數值已經達到最大時返回失敗 |
消費者:沒有數據時可以阻塞 | 消費者:沒有資源時可以阻塞 |
6.1.3 兩種信號量的對比
信號量的計數值都有限制:限定了最大值。如果最大值被限定為1,那么它就是二進制信號量;如果最大值不是1,它就是計數型信號量。
差別列表如下:
二進制信號量 | 技術型信號量 |
---|---|
被創建時初始值為0 | 被創建時初始值可以設定 |
其他操作是一樣的 | 其他操作是一樣的 |
6.2 信號量函數
使用信號量時,先創建、然后去添加資源、獲得資源。使用句柄來表示一個信號量。
6.2.1 創建
使用信號量之前,要先創建,得到一個句柄;使用信號量時,要使用句柄來表明使用哪個信號量。
對于二進制信號量、計數型信號量,它們的創建函數不一樣:
二進制信號量 | 計數型信號量 | |
---|---|---|
動態創建 |
xSemaphoreCreateBinary 計數值初始值為0 |
xSemaphoreCreateCounting |
vSemaphoreCreateBinary(過時了) 計數值初始值為1 |
||
靜態創建 | xSemaphoreCreateBinaryStatic | xSemaphoreCreateCountingStatic |
創建二進制信號量的函數原型如下:
/* 創建一個二進制信號量,返回它的句柄。
* 此函數內部會分配信號量結構體
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );
/* 創建一個二進制信號量,返回它的句柄。
* 此函數無需動態分配內存,所以需要先有一個StaticSemaphore_t結構體,并傳入它的指針
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );
創建計數型信號量的函數原型如下:
/* 創建一個計數型信號量,返回它的句柄。
* 此函數內部會分配信號量結構體
* uxMaxCount: 最大計數值
* uxInitialCount: 初始計數值
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
/* 創建一個計數型信號量,返回它的句柄。
* 此函數無需動態分配內存,所以需要先有一個StaticSemaphore_t結構體,并傳入它的指針
* uxMaxCount: 最大計數值
* uxInitialCount: 初始計數值
* pxSemaphoreBuffer: StaticSemaphore_t結構體指針
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t *pxSemaphoreBuffer );
6.2.2 刪除
對于動態創建的信號量,不再需要它們時,可以刪除它們以回收內存。
vSemaphoreDelete可以用來刪除二進制信號量、計數型信號量,函數原型如下:
/*
* xSemaphore: 信號量句柄,你要刪除哪個信號量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
6.2.3 give/take
二進制信號量、計數型信號量的give、take操作函數是一樣的。這些函數也分為2個版本:給任務使用,給ISR使用。列表如下:
在任務中使用 | 在ISR中使用 | |
---|---|---|
give | xSemaphoreGive | xSemaphoreGiveFromISR |
take | xSemaphoreTake | xSemaphoreTakeFromISR |
xSemaphoreGive的函數原型如下:
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
xSemaphoreGive函數的參數與返回值列表如下:
參數 | 說明 |
---|---|
xSemaphore | 信號量句柄,釋放哪個信號量 |
返回值 |
pdTRUE表示成功, 如果二進制信號量的計數值已經是1,再次調用此函數則返回失敗; 如果計數型信號量的計數值已經是最大值,再次調用此函數則返回失敗 |
pxHigherPriorityTaskWoken的函數原型如下:
BaseType_t xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
xSemaphoreGiveFromISR函數的參數與返回值列表如下:
參數 | 說明 |
---|---|
xSemaphore | 信號量句柄,釋放哪個信號量 |
pxHigherPriorityTaskWoken |
如果釋放信號量導致更高優先級的任務變為了就緒態, 則*pxHigherPriorityTaskWoken = pdTRUE |
返回值 |
pdTRUE表示成功, 如果二進制信號量的計數值已經是1,再次調用此函數則返回失?。?br /> 如果計數型信號量的計數值已經是最大值,再次調用此函數則返回失敗 |
xSemaphoreTake的函數原型如下:
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
xSemaphoreTake函數的參數與返回值列表如下:
參數 | 說明 |
---|---|
xSemaphore | 信號量句柄,獲取哪個信號量 |
xTicksToWait |
如果無法馬上獲得信號量,阻塞一會: 0:不阻塞,馬上返回 portMAX_DELAY: 一直阻塞直到成功 其他值: 阻塞的Tick個數,可以使用 pdMS_TO_TICKS() 來指定阻塞時間為若干ms |
返回值 | pdTRUE表示成功 |
xSemaphoreTakeFromISR的函數原型如下:
BaseType_t xSemaphoreTakeFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
xSemaphoreTakeFromISR函數的參數與返回值列表如下:
參數 | 說明 |
---|---|
xSemaphore | 信號量句柄,獲取哪個信號量 |
pxHigherPriorityTaskWoken |
如果獲取信號量導致更高優先級的任務變為了就緒態, 則*pxHigherPriorityTaskWoken = pdTRUE |
返回值 | pdTRUE表示成功 |
6.3 示例12: 使用二進制信號量來同步
本節代碼為: FreeRTOS_12_semaphore_binary
。
main函數中創建了一個二進制信號量,然后創建2個任務:一個用于釋放信號量,另一個用于獲取信號量,代碼如下:
/* 二進制信號量句柄 */
SemaphoreHandle_t xBinarySemaphore;
int main( void )
{
prvSetupHardware();
/* 創建二進制信號量 */
xBinarySemaphore = xSemaphoreCreateBinary( );
if( xBinarySemaphore != NULL )
{
/* 創建1個任務用于釋放信號量
* 優先級為2
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 創建1個任務用于獲取信號量
* 優先級為1
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
/* 啟動調度器 */
vTaskStartScheduler();
}
else
{
/* 無法創建二進制信號量 */
}
/* 如果程序運行到了這里就表示出錯了, 一般是內存不足 */
return 0;
}
發送任務、接收任務的代碼和執行流程如下:
- A:發送任務優先級高,先執行。連續3次釋放二進制信號量,只有第1次成功
- B:發送任務進入阻塞態
- C:接收任務得以執行,得到信號量,打印OK;再次去獲得信號量時,進入阻塞狀態
- 在發送任務的vTaskDelay退出之前,運行的是空閑任務:現在發送任務、接收任務都阻塞了
- D:發送任務再次運行,連續3次釋放二進制信號量,只有第1次成功
- E:發送任務進入阻塞態
- F:接收任務被喚醒,得到信號量,打印OK;再次去獲得信號量時,進入阻塞狀態
運行結果如下圖所示,即使發送任務連續釋放多個信號量,也只能成功1次。釋放、獲得信號量是一一對應的。
6.4 示例13: 防止數據丟失
本節代碼為: FreeRTOS_13_semaphore_circle_buffer
。
在示例12中,發送任務發出3次"提醒",但是接收任務只接收到1次"提醒",其中2次"提醒"丟失了。
這種情況很常見,比如每接收到一個串口字符,串口中斷程序就給任務發一次"提醒",假設收到多個字符、發出了多次"提醒"。當任務來處理時,它只能得到1次"提醒"。
你需要使用其他方法來防止數據丟失,比如:
在串口中斷中,把數據放入緩沖區
在任務中,一次性把緩沖區中的數據都讀出
簡單地說,就是:你提醒了我多次,我太忙只響應你一次,但是我一次性拿走所有數據
main函數中創建了一個二進制信號量,然后創建2個任務:一個用于釋放信號量,另一個用于獲取信號量,代碼如下:
/* 二進制信號量句柄 */
SemaphoreHandle_t xBinarySemaphore;
int main( void )
{
prvSetupHardware();
/* 創建二進制信號量 */
xBinarySemaphore = xSemaphoreCreateBinary( );
if( xBinarySemaphore != NULL )
{
/* 創建1個任務用于釋放信號量
* 優先級為2
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 創建1個任務用于獲取信號量
* 優先級為1
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
/* 啟動調度器 */
vTaskStartScheduler();
}
else
{
/* 無法創建二進制信號量 */
}
/* 如果程序運行到了這里就表示出錯了, 一般是內存不足 */
return 0;
}
發送任務、接收任務的代碼和執行流程如下:
- A:發送任務優先級高,先執行。連續寫入3個數據、釋放3個信號量:只有1個信號量起作用
- B:發送任務進入阻塞態
- C:接收任務得以執行,得到信號量
- D:接收任務一次性把所有數據取出
- E:接收任務再次嘗試獲取信號量,進入阻塞狀態
- 在發送任務的vTaskDelay退出之前,運行的是空閑任務:現在發送任務、接收任務都阻塞了
- F:發送任務再次運行,連續寫入3個數據、釋放3個信號量:只有1個信號量起作用
- G:發送任務進入阻塞態
- H:接收任務被喚醒,得到信號量,一次性把所有數據取出
程序運行結果如下,數據未丟失:
6.5 示例14: 使用計數型信號量
本節代碼為: FreeRTOS_14_semaphore_counting
。
使用計數型信號量時,可以多次釋放信號量;當信號量的技術值達到最大時,再次釋放信號量就會出錯。
如果信號量計數值為n,就可以連續n次獲取信號量,第(n+1)次獲取信號量就會阻塞或失敗。
main函數中創建了一個計數型信號量,最大計數值為3,初始值計數值為0;然后創建2個任務:一個用于釋放信號量,另一個用于獲取信號量,代碼如下:
/* 計數型信號量句柄 */
SemaphoreHandle_t xCountingSemaphore;
int main( void )
{
prvSetupHardware();
/* 創建計數型信號量 */
xCountingSemaphore = xSemaphoreCreateCounting(3, 0);
if( xCountingSemaphore != NULL )
{
/* 創建1個任務用于釋放信號量
* 優先級為2
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 創建1個任務用于獲取信號量
* 優先級為1
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
/* 啟動調度器 */
vTaskStartScheduler();
}
else
{
/* 無法創建信號量 */
}
/* 如果程序運行到了這里就表示出錯了, 一般是內存不足 */
return 0;
}
發送任務、接收任務的代碼和執行流程如下:
- A:發送任務優先級高,先執行。連續釋放4個信號量:只有前面3次成功,第4次失敗
- B:發送任務進入阻塞態
- CDE:接收任務得以執行,得到3個信號量
- F:接收任務試圖獲得第4個信號量時進入阻塞狀態
- 在發送任務的vTaskDelay退出之前,運行的是空閑任務:現在發送任務、接收任務都阻塞了
- G:發送任務再次運行,連續釋放4個信號量:只有前面3次成功,第4次失敗
- H:發送任務進入阻塞態
- IJK:接收任務得以執行,得到3個信號量
- L:接收任務再次獲取信號量時進入阻塞狀態
運行結果如下圖所示:
-
嵌入式
+關注
關注
5082文章
19104瀏覽量
304828 -
Linux
+關注
關注
87文章
11292瀏覽量
209334 -
RTOS
+關注
關注
22文章
811瀏覽量
119595 -
FreeRTOS
+關注
關注
12文章
484瀏覽量
62144 -
信號量
+關注
關注
0文章
53瀏覽量
8333
發布評論請先 登錄
相關推薦
評論