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