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