電影日記網(wǎng)站怎么做界首網(wǎng)站優(yōu)化公司
互斥量概述
在博文“?FreeRTOS_信號量 ”中,使用了二進制信號量實現(xiàn)了互斥,保護了串口資源。博文鏈接如下:
FreeRTOS_信號量-CSDN博客
但還是要引入互斥量的概念?;コ饬颗c二進制信號量相比,能夠多實現(xiàn)如下兩個功能:
- 防止優(yōu)先級反轉(zhuǎn)問題
- 解決遞歸上鎖/解鎖問題
互斥量有兩種:
- 普通互斥量:具有優(yōu)先級繼承功能,解決優(yōu)先級反轉(zhuǎn)問題
- 遞歸鎖:具有優(yōu)先級繼承功能、能夠解決遞歸上鎖/解鎖問題、解決誰上鎖誰才能解鎖的問題
優(yōu)先級反轉(zhuǎn)
優(yōu)先級反轉(zhuǎn)問題
假設(shè)任務(wù)A、B、C的優(yōu)先級為1、2、3。在程序開始時,A進行上鎖,之后B運行搶斷的A,之后C運行搶斷了B。在C中,想獲得鎖,但A已經(jīng)上鎖,所以C進入了阻塞,釋放了CPU。這時B繼續(xù)運行,但不解鎖,從而導(dǎo)致C被B搶占,即高優(yōu)先級的任務(wù)是否能繼續(xù)執(zhí)行由低優(yōu)先級的任務(wù)決定。
這種現(xiàn)象稱為優(yōu)先級反轉(zhuǎn)。
優(yōu)先級繼承
使用優(yōu)先級繼承的方法解決優(yōu)先級反轉(zhuǎn)的問題。
在C獲得鎖之后進入阻塞狀態(tài),同時執(zhí)行上鎖的任務(wù)A會繼承C的優(yōu)先級3,從而進行執(zhí)行。當(dāng)A執(zhí)行完解鎖后,A優(yōu)先級的優(yōu)先級變回原來的優(yōu)先級1。之后C獲得鎖,不再阻塞,C繼續(xù)執(zhí)行,執(zhí)行完成之后,B運行。
在低優(yōu)先級任務(wù)上鎖,高優(yōu)先級任務(wù)獲得鎖阻塞之后,低優(yōu)先級任務(wù)繼承高優(yōu)先級任務(wù)的優(yōu)先級的操作叫做優(yōu)先級繼承。
遞歸上鎖/解鎖
遞歸上鎖原因
遞歸上鎖的示例代碼如下:
void fun(){xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);/* 上鎖 *//* 一些功能 */xSemaphoreGive(SemaphoreHandleTest);/* 解鎖 */
}
void Task1(void *param){while(1){xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);/* 上鎖 *//* 一些功能 */fun();xSemaphoreGive(SemaphoreHandleTest);/* 解鎖 */}
}
在上述代碼中,任務(wù)1首先進行了上鎖,之后調(diào)用了fun函數(shù)。在fun函數(shù)中又進行了上鎖,但這時已經(jīng)上鎖,所以就阻塞在了fun函數(shù)中。最終導(dǎo)致Task1無法執(zhí)行完成fun函數(shù),也就無法執(zhí)行解鎖函數(shù),形成死鎖。
遞歸鎖
使用遞歸鎖可以解決上述問題。遞歸鎖的作用是,如果開始時為A來上鎖,那么在上鎖之后,A依舊可以進行調(diào)用上鎖函數(shù)進行上鎖,而不會進入阻塞狀態(tài)。但不管怎樣,上鎖之后都要解鎖,即:上鎖多少次,就要解鎖多少次,上鎖與解鎖一 一配對。
除此之外,遞歸鎖還可以滿足誰上鎖,誰才有權(quán)力解鎖的問題。而信號量和普通互斥鎖并不能實現(xiàn)這個功能。
相關(guān)配置
在使用互斥量之前,需要打開宏開關(guān),具體的步驟如下:
在使用遞歸鎖之前,需要打開宏開關(guān),具體的步驟如下:
互斥量相關(guān)函數(shù)
創(chuàng)建普通互斥量
函數(shù)聲明如下:
/* 這是一個宏,創(chuàng)建普通互斥量 */
xSemaphoreCreateMutex()/* 宏定義 */
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )/* 實際調(diào)用函數(shù) */
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
返回值:互斥量的句柄
互斥量在創(chuàng)建后與二進制信號量不同,互斥量初始值為1,二進制信號量初始值為0
獲取/釋放互斥量
獲取和釋放互斥量所用的函數(shù)與二值信號量的函數(shù)一致。
函數(shù)聲明如下:
/* 這是一個宏,存入(釋放)信號量/互斥量 */
xSemaphoreGive( xSemaphore )/* 這是一個宏,獲取信號量/互斥量 */
xSemaphoreTake( xSemaphore, xBlockTime )
存入函數(shù)返回值:成功返回pdPASS,在當(dāng)前計數(shù)值=最大計數(shù)值時,會存入失敗
xSemaphore :信號量句柄
xBlockTime :阻塞等待時間,portMAX_DELAY為死等
創(chuàng)建遞歸鎖
函數(shù)聲明如下:
/* 這是一個宏,創(chuàng)建遞歸鎖 */
xSemaphoreCreateRecursiveMutex()/* 宏定義 */
#define xSemaphoreCreateRecursiveMutex() \xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )/* 實際調(diào)用函數(shù) */
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
返回值:互斥量的句柄
創(chuàng)建后初始值為1
獲取/釋放遞歸鎖
獲取和釋放遞歸鎖的函數(shù)用法與互斥量一致,只是名字有所差別
函數(shù)聲明如下:
/* 這是一個宏,存入(釋放)遞歸鎖 */
xSemaphoreGiveRecursive( xMutex )/* 這是一個宏,獲取遞歸鎖 */
xSemaphoreTakeRecursive( xMutex, xBlockTime )
存入函數(shù)返回值:成功返回pdPASS,在當(dāng)前計數(shù)值=最大計數(shù)值時,會存入失敗
xSemaphore :信號量句柄
xBlockTime :阻塞等待時間,portMAX_DELAY為死等
驗證實驗
1、基本互斥實驗
使用互斥量來確保串口的輸出不被打斷。
與二進制信號量實現(xiàn)該功能相比,代碼方面的差異只在初始化不同,其他代碼都沒有進行修改
二進制信號量實現(xiàn)該功能的初始化代碼如下:
/* 創(chuàng)建二進制信號量,初始值自動設(shè)置為 0 */
SemaphoreHandleTest = xSemaphoreCreateBinary();
/* 讓信號量為1,代表串口資源可用 */
xSemaphoreGive(SemaphoreHandleTest);
互斥量實現(xiàn)該功能的初始化代碼如下:
/* 創(chuàng)建互斥量,初始值自動設(shè)置為 1 */
SemaphoreHandleTest = xSemaphoreCreateMutex();
實驗現(xiàn)象和其他代碼實現(xiàn)與博文“?FreeRTOS_信號量 ”中 “ 驗證實驗 ” 均一致。博文鏈接如下:
FreeRTOS_信號量-CSDN博客
2、優(yōu)先級繼承實驗
根據(jù)本文 “互斥量概述” 中 “?優(yōu)先級反轉(zhuǎn) ”內(nèi)容的描述,來進行編寫代碼。這里使用二進制信號量與互斥量進行運行結(jié)果的對比。
具體的代碼實現(xiàn)如下:
QueueHandle_t SemaphoreHandleTest;
char taskA_flag = 0;
char taskB_flag = 0;
char taskC_flag = 0;
void TaskAFunction(void *param){int i=0;while(1){/* 上鎖 */xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);/* 執(zhí)行一個很長的時間的程序 */taskA_flag = 1;taskB_flag = 0;taskC_flag = 0;for(i=0;i<20;i++){printf("%d,%d,%d",taskA_flag,taskB_flag,taskC_flag);taskA_flag = 1;taskB_flag = 0;taskC_flag = 0;}/* 解鎖 */xSemaphoreGive(SemaphoreHandleTest);}
}
void TaskBFunction(void *param){vTaskDelay(5);/* B先休眠,讓A任務(wù)執(zhí)行 */while(1){/* B一直在執(zhí)行,這會導(dǎo)致如果A優(yōu)先級低于B,則A一直不執(zhí)行 */taskA_flag = 0;taskB_flag = 1;taskC_flag = 0;}
}
void TaskCFunction(void *param){int i=0;vTaskDelay(10);/* C先休眠,讓AB任務(wù)執(zhí)行 */while(1){taskA_flag = 0;taskB_flag = 0;taskC_flag = 1;/* 上鎖 */xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);/* C任務(wù)執(zhí)行一段時間 */for(i=0;i<10;i++){printf("this is taskC\r\n");taskA_flag = 0;taskB_flag = 0;taskC_flag = 1;}/* 解鎖 */xSemaphoreGive(SemaphoreHandleTest);}
}
int main( void )
{TaskHandle_t xHandleTask1;TaskHandle_t xHandleTask2;TaskHandle_t xHandleTask3;prvSetupHardware();SerialPortInit();printf("UART TEST\r\n");/* 創(chuàng)建互斥量,初始值自動設(shè)置為 1 *///SemaphoreHandleTest = xSemaphoreCreateMutex();/* 創(chuàng)建二進制信號量,初始值自動設(shè)置為 0 */SemaphoreHandleTest = xSemaphoreCreateBinary();/* 讓信號量為1,代表資源可用 */xSemaphoreGive(SemaphoreHandleTest);xTaskCreate(TaskAFunction,"TaskA",100,(void*)NULL,1,&xHandleTask1);xTaskCreate(TaskBFunction,"TaskB",100,(void*)NULL,2,&xHandleTask2);xTaskCreate(TaskCFunction,"TaskC",100,(void*)NULL,3,&xHandleTask3);vTaskStartScheduler();return 0;
}
該代碼的主要功能就是利用taskA_flag 、taskB_flag、taskC_flag 這三個標(biāo)志位來判斷當(dāng)前任務(wù)是誰在運行。通過邏輯分析儀顯示出這三個變量的電平來分析搶占關(guān)系。
二值信號量的運行結(jié)果如下:
可以看到,在1 -> 2階段中,BC調(diào)用延時處于阻塞態(tài),因此A運行。在2->3階段中,A運行了一段時間后,B的延時結(jié)束,B開始搶占A進行運行。在2->3階段中,B運行了一段時間后,C的延時結(jié)束,C開始搶占B進行運行。以上是正常的搶占流程。
在C搶占B之后,嘗試獲取信號量,但信號量已經(jīng)被A所獲取,還未進行釋放,因此C進入了阻塞狀態(tài),之后B進行搶占到CPU繼續(xù)執(zhí)行。但B的優(yōu)先級高于A,A不能夠釋放信號量,這樣就導(dǎo)致了C永遠處于阻塞態(tài),只有當(dāng)B釋放CPU,A才能釋放信號量,從而C才可以運行,這樣就產(chǎn)生了優(yōu)先級反轉(zhuǎn)的問題。
互斥量的運行結(jié)果如下:
可以看到,在1 -> 2階段中,BC調(diào)用延時處于阻塞態(tài),因此A運行。在2->3階段中,A運行了一段時間后,B的延時結(jié)束,B開始搶占A進行運行。在2->3階段中,B運行了一段時間后,C的延時結(jié)束,C開始搶占B進行運行。以上是正常的搶占流程。
在C搶占B之后,嘗試獲取互斥量,但互斥量已經(jīng)被A所獲取,還未進行釋放,因此A進行優(yōu)先級的繼承,此時A的優(yōu)先級變成了3,即:4 -> 5階段。在5->6階段中,A的優(yōu)先級為3,執(zhí)行完成打印工作后,對互斥量進行了解鎖,這時任務(wù)A的優(yōu)先級變回原來的優(yōu)先級1,C任務(wù)搶占A開始執(zhí)行。
3、誰上鎖,誰才能解鎖實驗
普通互斥量和二進制信號量并未實現(xiàn)誰上鎖,誰才能解鎖,這里使用普通互斥量與遞歸鎖來進行對比,來驗證遞歸鎖能夠?qū)崿F(xiàn)誰上鎖,誰才能解鎖的功能。
具體代碼實現(xiàn)如下:
QueueHandle_t SemaphoreHandleTest;void PrintFunction(void *param){while(1){// /* 以下為互斥量測試 */
// xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);
// printf("%s",(char*)param);
// xSemaphoreGive(SemaphoreHandleTest);/* 以下為遞歸鎖測試 */xSemaphoreTakeRecursive(SemaphoreHandleTest,portMAX_DELAY);printf("%s",(char*)param);xSemaphoreGiveRecursive(SemaphoreHandleTest);vTaskDelay(1);}
}
void BreakSemaphoreTask(void *param){vTaskDelay(1);while(1){// /* 以下為互斥量測試 */
// while(1){
//
// /* 如果獲得不到鎖,就直接往里面放一把鎖 */
// if(xSemaphoreTake(SemaphoreHandleTest,0) == pdFALSE){
// xSemaphoreGive(SemaphoreHandleTest);
// }else{
// break;
// }
// }
// printf("this is break\r\n");
// xSemaphoreGive(SemaphoreHandleTest);/* 以下為遞歸鎖測試 */while(1){/* 如果獲得不到鎖,就直接往里面放一把鎖 */if(xSemaphoreTakeRecursive(SemaphoreHandleTest,0) == pdFALSE){xSemaphoreGiveRecursive(SemaphoreHandleTest);}else{break;}}printf("this is break\r\n");xSemaphoreGiveRecursive(SemaphoreHandleTest);vTaskDelay(1);}
}int main( void )
{TaskHandle_t xHandleTask1;TaskHandle_t xHandleTask2;TaskHandle_t xHandleTask3;prvSetupHardware();SerialPortInit();printf("UART TEST\r\n");// /* 創(chuàng)建互斥量,初始值自動設(shè)置為 1 */
// SemaphoreHandleTest = xSemaphoreCreateMutex();/* 創(chuàng)建遞歸鎖,初始值自動設(shè)置為 1 */SemaphoreHandleTest = xSemaphoreCreateRecursiveMutex();xTaskCreate(PrintFunction,"TaskA",100,(void*)"this is TaskA\r\n",1,&xHandleTask1);xTaskCreate(PrintFunction,"TaskB",100,(void*)"this is TaskB\r\n",1,&xHandleTask2);xTaskCreate(BreakSemaphoreTask,"TaskC",100,(void*)NULL,1,&xHandleTask3);vTaskStartScheduler();return 0;
}
互斥量的運行結(jié)果如下:
可以看到,打印結(jié)果亂套了。這是因為在A上鎖之后,C任務(wù)去放進去了一把鎖,導(dǎo)致B任務(wù)原來在阻塞,突然獲得了鎖,執(zhí)行了B任務(wù)中的printf。因此互斥量并沒有實現(xiàn)誰上鎖,誰才能夠解鎖的功能。
遞歸鎖的運行結(jié)果如下:
可以看到,打印變得非常正常。這是因為遞歸鎖可以實現(xiàn)誰上鎖,誰才能夠解鎖,因此不能夠再C中解開A的鎖,從而執(zhí)行結(jié)果變得正常。
4、遞歸上鎖實驗
在“?誰上鎖,誰才能解鎖實驗 ”的代碼基礎(chǔ)上進行修改,將打印函數(shù)修改為如下情況:
void PrintFunction(void *param){int i=0;while(1){// /* 以下為互斥量測試 */
// xSemaphoreTake(SemaphoreHandleTest,portMAX_DELAY);
// printf("%s",(char*)param);
// xSemaphoreGive(SemaphoreHandleTest);/* 以下為遞歸鎖測試 */xSemaphoreTakeRecursive(SemaphoreHandleTest,portMAX_DELAY);for(i=0;i<5;i++){/* 遞歸上鎖,上鎖之后再上一次鎖 */xSemaphoreTakeRecursive(SemaphoreHandleTest,portMAX_DELAY);printf("lock:%i\r\n",i);xSemaphoreGiveRecursive(SemaphoreHandleTest);}printf("%s",(char*)param);xSemaphoreGiveRecursive(SemaphoreHandleTest);vTaskDelay(1);}
}
運行結(jié)果如下:
可以看到lock0~4正常打印,說明并未產(chǎn)生阻塞的情況,遞歸鎖可以遞歸上鎖。