習(xí)(十七)——優(yōu)先級(jí)翻轉(zhuǎn)與優(yōu)先級(jí)繼承)
上一節(jié)學(xué)習(xí)了互斥量Mutex它可以保護(hù)共享資源保證同一時(shí)刻只有一個(gè)任務(wù)能夠訪問串口、LCD、Flash等資源。但是如果多個(gè)不同優(yōu)先級(jí)的任務(wù)同時(shí)競(jìng)爭(zhēng)一個(gè)互斥量就可能產(chǎn)生一個(gè)比較隱蔽的問題——優(yōu)先級(jí)翻轉(zhuǎn)Priority Inversion。這一節(jié)學(xué)習(xí)什么是優(yōu)先級(jí)翻轉(zhuǎn)以及FreeRTOS是如何利用**優(yōu)先級(jí)繼承Priority Inheritance**解決這一問題的。這也是嵌入式軟件工程師面試中經(jīng)常出現(xiàn)的知識(shí)點(diǎn)。一、什么是優(yōu)先級(jí)翻轉(zhuǎn)假設(shè)系統(tǒng)中有三個(gè)任務(wù)Task_H 高優(yōu)先級(jí)3Task_M 中優(yōu)先級(jí)2Task_L 低優(yōu)先級(jí)1其中Task_L首先獲得了互斥量Mutex開始訪問串口。此時(shí)Task_H也需要訪問串口。但是Mutex已經(jīng)被Task_L占用因此Task_H ↓ 等待Mutex按理來說只需要等待Task_L執(zhí)行完釋放互斥量即可。但是Task_M此時(shí)變?yōu)榫途w態(tài)。由于Task_M 優(yōu)先級(jí) Task_L于是CPU開始執(zhí)行Task_M而Task_L一直得不到CPU。結(jié)果就是Task_H ↓ 等待Task_L ↓ Task_L又一直被Task_M搶占最終高優(yōu)先級(jí)任務(wù) 反而等待低優(yōu)先級(jí)任務(wù)。這就是優(yōu)先級(jí)翻轉(zhuǎn)二、優(yōu)先級(jí)翻轉(zhuǎn)過程整個(gè)過程可以表示為Task_L 獲得Mutex ↓ Task_H需要Mutex ↓ Task_H阻塞 ↓ Task_M開始運(yùn)行 ↓ Task_L沒有機(jī)會(huì)運(yùn)行 ↓ Mutex一直不能釋放 ↓ Task_H持續(xù)等待可以看到真正阻塞高優(yōu)先級(jí)任務(wù)的不是Task_L而是Task_M這也是優(yōu)先級(jí)翻轉(zhuǎn)最容易讓人困惑的地方。三、為什么優(yōu)先級(jí)翻轉(zhuǎn)危險(xiǎn)假設(shè)Task_H負(fù)責(zé)電機(jī)控制而Task_M只是LED閃爍如果發(fā)生優(yōu)先級(jí)翻轉(zhuǎn)LED任務(wù) 一直搶占CPU ↓ 電機(jī)控制一直等待顯然是不合理的。在實(shí)時(shí)系統(tǒng)中高優(yōu)先級(jí)任務(wù) 必須盡快得到CPU。因此必須解決優(yōu)先級(jí)翻轉(zhuǎn)問題。四、什么是優(yōu)先級(jí)繼承FreeRTOS互斥量支持Priority Inheritance即優(yōu)先級(jí)繼承。它的思想很簡(jiǎn)單當(dāng)Task_H等待Mutex而Mutex被Task_L占有時(shí)。FreeRTOS會(huì)暫時(shí)把Task_L的優(yōu)先級(jí)提升為Task_H的優(yōu)先級(jí)。例如原來Task_L Priority 1現(xiàn)在自動(dòng)變成Priority 3這樣Task_M Priority 2就不能搶占Task_L了。五、優(yōu)先級(jí)繼承過程加入優(yōu)先級(jí)繼承以后Task_L獲得Mutex ↓ Task_H等待Mutex ↓ Task_L優(yōu)先級(jí)提升 ↓ Task_L繼續(xù)運(yùn)行 ↓ 釋放Mutex ↓ Task_L恢復(fù)原優(yōu)先級(jí) ↓ Task_H立即運(yùn)行整個(gè)過程中Task_M不會(huì)再影響Task_H等待互斥量。因此優(yōu)先級(jí)翻轉(zhuǎn)被解決。六、代碼示例創(chuàng)建互斥量SemaphoreHandle_t Mutex; Mutex xSemaphoreCreateMutex();任務(wù)1void LowTask(void *pvParameters) { while(1) { xSemaphoreTake( Mutex, portMAX_DELAY ); printf(Low Task\r\n); vTaskDelay(pdMS_TO_TICKS(2000)); xSemaphoreGive(Mutex); vTaskDelay(pdMS_TO_TICKS(1000)); } }任務(wù)2void HighTask(void *pvParameters) { while(1) { xSemaphoreTake( Mutex, portMAX_DELAY ); printf(High Task\r\n); xSemaphoreGive(Mutex); vTaskDelay(pdMS_TO_TICKS(500)); } }程序運(yùn)行過程中如果LowTask已經(jīng)獲得互斥量。隨后HighTask請(qǐng)求同一個(gè)互斥量。FreeRTOS會(huì)自動(dòng)進(jìn)行優(yōu)先級(jí)繼承。整個(gè)過程不需要用戶手動(dòng)修改優(yōu)先級(jí)。七、為什么二值信號(hào)量不能解決很多初學(xué)者容易認(rèn)為二值信號(hào)量 ↓ 也能鎖資源。事實(shí)上二值信號(hào)量 沒有優(yōu)先級(jí)繼承。因此如果使用xSemaphoreCreateBinary();保護(hù)共享資源。發(fā)生優(yōu)先級(jí)翻轉(zhuǎn)以后。FreeRTOS不會(huì)自動(dòng)提高低優(yōu)先級(jí)任務(wù)的優(yōu)先級(jí)。所以保護(hù)共享資源 推薦使用Mutex 不要使用Binary Semaphore。八、什么時(shí)候會(huì)發(fā)生優(yōu)先級(jí)翻轉(zhuǎn)通常需要滿足三個(gè)條件第一多個(gè)任務(wù)優(yōu)先級(jí)不同。第二多個(gè)任務(wù)共同訪問同一個(gè)共享資源。第三共享資源采用互斥機(jī)制保護(hù)。例如USART SPI I2C Flash SD卡這些都是實(shí)際工程中經(jīng)常發(fā)生優(yōu)先級(jí)翻轉(zhuǎn)的地方。九、實(shí)際工程建議互斥量雖然能夠解決優(yōu)先級(jí)翻轉(zhuǎn)但仍然要盡量縮短Take ↓ Give之間的代碼。例如xSemaphoreTake(); printf(); xSemaphoreGive();是合理的。但是xSemaphoreTake(); vTaskDelay(5000); xSemaphoreGive();則不建議這樣寫。因?yàn)槿蝿?wù)長(zhǎng)時(shí)間占有互斥量會(huì)增加其他任務(wù)等待時(shí)間降低系統(tǒng)實(shí)時(shí)性。因此共享資源保護(hù)范圍越小越好互斥量占用時(shí)間越短越好。十、總結(jié)這一節(jié)學(xué)習(xí)了FreeRTOS中的優(yōu)先級(jí)翻轉(zhuǎn)與優(yōu)先級(jí)繼承。當(dāng)高優(yōu)先級(jí)任務(wù)等待低優(yōu)先級(jí)任務(wù)占有的互斥量而低優(yōu)先級(jí)任務(wù)又被中優(yōu)先級(jí)任務(wù)搶占時(shí)就會(huì)發(fā)生優(yōu)先級(jí)翻轉(zhuǎn)。FreeRTOS互斥量支持優(yōu)先級(jí)繼承機(jī)制能夠臨時(shí)提高持有互斥量任務(wù)的優(yōu)先級(jí)使其盡快釋放共享資源從而減少高優(yōu)先級(jí)任務(wù)的等待時(shí)間。因此需要牢記二值信號(hào)量用于任務(wù)同步?;コ饬坑糜诠蚕碣Y源保護(hù)?;コ饬烤哂袃?yōu)先級(jí)繼承而二值信號(hào)量沒有。