關注+星標公眾號,不錯過精彩內容

轉自?|?麥克泰技術
內存保護單元(MPU)是一種硬件機制,通過只允許代碼訪問需要的內存和外設來提高嵌入式設備的安全性。應用程序可以組織為進程(process),每個進程訪問自己的內存和外設。MPU不僅阻止應用程序代碼訪問其指定區域以外的內存或外設,而且還可以用于檢測堆棧溢出。
我們基于ARM Cortex-M MCU中的MPU,討論一下MPU所提供的一些特性。
什么是MPU?

圖1 應用隔離為進程
進程間可以通過共享內存進行通信,兩個進程的MPU配置表中將出現相同的區域。
應用中也可以包含具有完全權限的系統級任務和ISR,允許它們訪問所有內存、外設或CPU。
當違規發生時,系統的行為取決于應用程序,是哪個任務違規。例如,如果違規是由圖形用戶界面(GUI)造成的,可以終止并重新啟動GUI,并且不會影響系統的其它部分。但是,如果違規任務控制一個制動器,則異常處理程序可能需要在重啟任務之前立即停止制動器。理想情況下,在產品開發過程中會捕獲并糾正訪問違規,否則,系統設計人員將需要評估所有可能的結果,并決定發生這種情況時該做什么。
使用MPU檢測堆棧溢出
在基于RTOS的應用中,每個任務都需要單獨的堆棧空間。堆棧溢出可能是基于RTOS的系統開發人員所面臨的最常見的問題之一。如果沒有硬件幫助,堆棧溢出檢測可以由軟件實現,但軟件方式不能及時捕獲溢出,可能導致產品不穩定。MPU可以幫助防止堆棧溢出。
如圖2所示,MPU域可用于堆棧溢出檢測。使用一個小的域(RedZone)來覆蓋每個任務堆棧的底部。配置MPU屬性,如果有任何代碼嘗試寫入該區域,將觸發MPU異常。域的大小決定了該方法在捕獲堆棧溢出方面的效率。區域越大,堆棧溢出捕獲的可能性就越大,同時堆棧可用的RAM就越少。換言之,RedZone域被認為是不可用的內存,它被用來檢測非法寫入。開始時可以將RedZone大小設置為32個字節,如果任務堆棧為512個字節,那么32個字節約占用6%,剩余480個字節的可用堆棧空間。

圖2 使用MPU域檢測堆棧溢出
另一種檢測方法是將整個任務堆棧封裝為一個MPU區域,允許讀寫操作。該方法有兩個問題:首先,在Cortex-M(ARMv7M架構)上,堆棧的大小需為2的冪,并且必須在冪邊界(即32、64、128、256、512等)上對齊。如果嵌入式應用有足夠的RAM,那么這不是問題;然而,在資源受限的應用 (如Cortex-M MCU)中,在減少浪費的同時設置合適的堆棧內存會很復雜。其次,該方法不允許寫同一進程中的其它任務堆棧,不允許進程中的任務通過堆棧傳遞信息。
進程表
進程表中的條目數量取決于MPU。Cortex-M架構MCU可以有8個或16個MPU區域。由于可用的區域數量有限,通常更多設置區域保護RAM中的數據訪問。但是,如果應用程序沒有用完所有區域,也可以通過區域限制對代碼的訪問來提高安全性。
圖3展示了一個包含四個任務的進程的區域定義,所有進程將共享相同的Flash代碼空間。

圖3 一個擁有4個任務的進程MPU域
1、一個MPU域提供對代碼空間的讀取和執行訪問。因為常量通常存儲在Flash中(ASCII字符串、查找表、常量等),所以讀訪問是必須的。
2、一個區域用于提供進程相關的外設訪問,例如,以太網控制器、USB控制器等。MPU區域設置為讀寫訪問,但不允許執行代碼。如果進程不需要訪問外設,則不需要此MPU區域。
3、一個MPU區域封裝進程全局變量以及堆(heap)空間。MPU區域將設置為讀寫權限,但同樣不允許執行代碼。
4、一個MPU區域用于檢測堆棧溢出。此方法假設進程中的任務不會通過其堆棧共享數據。同樣,在此區域中不允許執行代碼。RTOS負責選擇運行哪個任務,相應任務的堆棧將被封裝在MPU區域中。
5、該區域顯示了由于MPU區域大小和對齊限制而可能導致的RAM浪費。資源受限的嵌入式應用中應嘗試盡量減少浪費。
6、此MPU區域用于建立多個進程共享的RAM。如果進程不需要共享數據,則不需要此MPU區域。
進程表由“N”個條目組成,每個條目包含兩個字段:區域的基地址和指定區域屬性(區域大小,允許讀、寫或執行等)。
進程表在創建任務時被分配給任務。RTOS只是在任務的控制塊(TCB)中保留一個指向進程表的指針。RTOS在上下文切換時增加更新MPU進程表的代碼,如圖4所示。在切換任務時不需要保存MPU配置。

圖4 RTOS上下文切換時,更新MPU配置
Cortex-M 特權等級
上電后,Cortex-M運行在特權模式,可以訪問CPU的所有資源,訪問任何內存或I/O地址,啟用/禁用中斷,設置嵌套向量中斷控制器(NVIC),配置FPU和MPU等。
為了保證系統的安全,特權模式代碼保留給經過完全測試并受信任的代碼。由于大多數RTOS都經過了完整的測試,通常被認為是值得信任的,而應用代碼是不可信的。也有例外,例如,只要ISR保持盡可能短而不被濫用,ISR通常被認為是受信任的,因此也以特權模式運行。這是大多數RTOS供應商的建議。
應用代碼在非特權模式下運行,從而限制了代碼可以做的事情。具體來說,非特權模式可以防止代碼關中斷、更改嵌套向量中斷控制器(NVIC)的設置、將運行模式更改為特權等級、修改MPU設置等。這是一個理想的特性,因為我們不希望不受信任的代碼賦予自己特權,從而更改系統設計者提供的保護。
由于CPU總是以特權模式開始運行,任務需要從創建時就以非特權模式運行,或者在啟動后通過API調用,切換到非特權模式。一旦進入非特權模式,CPU只有在中斷或異常服務中才能切換回特權模式。
在用戶模式訪問RTOS服務
由于非特權代碼不能禁用中斷,因此應用代碼被迫使用RTOS服務來訪問共享資源。由于RTOS服務在特權模式下運行,因此非特權任務必須通過Cortex-M提供的SVC機制切換回特權模式。SVC的行為類似中斷,但由CPU指令觸發。
在Cortex-M上,SVC指令使用一個8位參數來指定256個可能的RTOS服務。設計者決定非特權代碼可以使用的RTOS服務。例如,你可能不希望允許非特權任務終止其它任務(或其本身)。此外,這些服務都不允許禁用中斷,因為這將破壞在非特權模式下運行代碼的目的。一旦調用,SVC指令跳轉到SVC異常處理程序。
SVC處理過程如圖5所示。
圖5 用戶代碼調用RTOS服務
在Cortex-M3上,SVC處理程序將增加約1k字節的代碼,需要執行75~125條CPU指令。因此,相同RTOS服務,在非特權代碼中調用比特權模式調用需要更多的處理時間。
在非特權模式下運行代碼還可以防止用戶代碼禁用中斷,從而減少了鎖定系統的機會。當然,如果用戶代碼進入無限循環,特別是在高優先級任務或ISR中時,鎖定仍然可能發生。在這種情況下,通過使用看門狗可以恢復。
進程間通信
圖6展示了進程之間的通信方式。這些只是一些可能的場景,實際上,應用程序可以使用這些技術的組合。

?圖6 進程通信方式
1、互斥量用于確保兩個進程不同時訪問相同的數據。注意,互斥量駐留在RTOS內存空間中,通過RTOS API,所有進程可以訪問該互斥量。
2、需要訪問受保護資源的任務必須首先獲取互斥量。當任務完成共享資源訪問后,互斥量將被釋放。沙漏表示可選超時,當任務不希望永久等待所有者釋放互斥量時,可以使用超時機制。
3、信號量也可以用來指示數據可用。
4、進程中的任務將數據存儲到共享內存中,然后發出信號。
5、進程B中的任務等待進程A的信號。沙漏表示一個可選的超時,以避免永遠等待信號。如果該信號未在規定的時間內發生,則RTOS將恢復該任務。在這種情況下,任務知道共享區域中沒有存入任何東西。
6、如果沒有發生超時,進程B確認數據處理完成。
7、進程A發信號量后,等待具有可選超時的確認。
8、通信也可以使用RTOS的消息隊列機制。此時,從共享RAM區域動態分配緩沖區,進程A中的發送任務填充緩沖區,并將指針發送給進程B中的任務。
9、與信號量情況類似,等待進程B中的任務確認,并指定一個可選的超時。
內存和I/O訪問錯誤處理
進程間可以通過共享內存進行通信,兩個進程的MPU配置表中將出現相同的區域。
應用中也可以包含具有完全權限的系統級任務和ISR,允許它們訪問所有內存、外設或CPU。
當違規發生時,系統的行為取決于應用程序,是哪個任務違規。例如,如果違規是由圖形用戶界面(GUI)造成的,可以終止并重新啟動GUI,并且不會影響系統的其它部分。但是,如果違規任務控制一個制動器,則異常處理程序可能需要在重啟任務之前立即停止制動器。理想情況下,在產品開發過程中會捕獲并糾正訪問違規,否則,系統設計人員將需要評估所有可能的結果,并決定發生這種情況時該做什么。
MPU的工作是確保進程中的任務只能訪問分配給它的內存和外設。但是,如果任務試圖訪問允許區域以外的數據呢?MPU會觸發一個稱為內存管理(MemManage)故障的CPU異常。
當故障發生時,系統行為取決于應用程序,但如何處理故障可能是很難確定的事情。首先,這些類型的故障應該在開發過程中被檢測和糾正。然而,使用MPU的原因之一是為了防止發生的無效內存或外設訪問,要么是因為系統驗證期間未捕獲某些偶發情況,或者是未經授權的訪問。
MemManage故障通常由RTOS處理。理想情況下,嵌入式系統有一些機制可以記錄和報告故障,以便在產品的下一個版本中修正。文件系統是記錄這些故障的好地方,當然,還取決于故障處理程序。
發生故障時,故障處理程序可以執行以下操作序列(偽代碼):
void OS_MPU_FaultHandler (void){// Terminate the offending task/process (1)// Release resources owned by the task/process (2)// Run a user provided ‘callback’ (based on the offending task) (3)// If we have a file system: (4)// Store information about the cause// Do we restart the task/process? (5)// Yes, Restart the task/process// Alert a user (6)// No, Reset the system (7)}
(1)當故障發生時,設計者需要確定如何操作。至少必須終止違規的任務,但我們是否還需要終止此進程中的其他任務?沒有一個確定的答案,事實上,這可能取決于是哪個任務造成了故障。因此,MPU故障處理程序應根據觸發它的任務或進程執行不同的操作。
(2)被終止的違規任務(或進程)可能擁有內核對象、緩沖區、I/O等資源。這些資源需要被釋放,以避免影響其他任務/進程。
(3)導致故障的任務可能會控制制動器或其他類型的輸出,需將任務置于安全狀態,以避免對人員或資產造成傷害。嵌入式系統設計者應提供用戶定義的回調函數,以處理系統特定的操作。在任務創建過程中,將回調函數存儲在任務的控制塊(TCB)中。為提高系統安全性,只能在系統啟動時創建任務,此時CPU處于特權模式;運行時只能在故障時刪除任務。由于TCB位于RTOS空間中,因此無法從用戶代碼訪問回調函數,從而防止潛在的不安全和不可靠的代碼無意中或惡意地調用回調函數。
(4)如果嵌入式系統具有數據存儲功能,則可以記錄故障相關的信息,如違規任務的性質、CPU寄存器的值、所采取的操作等。
(5)根據導致故障的任務,可以重新啟動,使系統可以錯誤中恢復。
(6)如果系統能夠恢復,并且如果系統包含顯示,則警告提示非常有用。此外,如果系統具有網絡連接,則通知服務部門和開發團隊可以在將來的版本中避免此問題。
(7)如果系統無法恢復,除了重置系統之外,可能沒有其他選擇。
可以更改MPU進程表,使其包含每個任務的回調函數,以便檢測到故障時從RTOS上下文切換代碼調用。如果所有的任務都需要對故障執行相同的操作,那么可以不使用此功能,或者讓所有MPU進程表都指向相同的回調函數。后一種選擇更最靈活,因為它為未來的版本提供了更大的靈活性。但你可能需要咨詢RTOS供應商,以確定此功能是否可用。
建議


