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