在本系列關于將 RTOS 與 MPU 結合使用的最后一部分中,我們將了解如何按進程對 RAM 進行分組,并以使用 Cortex-M MPU 時的建議列表作為結尾。
到目前為止,我們已經了解了 MPU 是什么以及它如何幫助將任務和流程相互隔離。我們還研究了如何設置 Cortex-M MPU,發現它非常易于使用。使用 MPU 的復雜性更多地與組織應用程序的內存有關,而不是更新這個非常有用的設備的機制。
在本系列關于將 RTOS 與 MPU 結合使用的最后一部分中,我們將了解如何按進程對 RAM 進行分組,并以使用 Cortex-M MPU 時的建議列表作為結尾。
創建 MPU 進程表
使用 MPU 時最大的困難可能是按進程對內存進行分組并創建 MPU 進程表。這部分是因為您需要更深入地了解您的工具鏈:編譯器、匯編器和鏈接器/定位器。
假設我使用的是 IAR 工具鏈(即 EWARM),但概念非常相似,您可以根據自己使用的工具調整這些概念。除非另有說明,否則鏈接器會將數據(即 RAM)放置在圖 2 所示的三個部分之一中。
未初始化的數據
零初始化數據
初始化數據
顧名思義,未初始化的數據對應于在編譯時未賦予初始值或未聲明為靜態的變量。
零初始化數據對應于聲明為靜態并在啟動時初始化為零的數據。鏈接器將其分組為一個連續的塊,以便啟動代碼可以執行塊集(為 0)。
初始化數據對應于具有初始值的數據(例如 int x = 10;)。同樣,鏈接器將這些數據分組到一個連續的塊中,但在 ROM 中創建一個并行塊,其中包含 RAM 中每個相應變量的初始值。啟動時,整個塊從 ROM 復制到 RAM。
【圖2 | RAM 部分。]
如前所述,進程的 RAM 必須連續分組,如圖 3 所示。為此,我們需要繞過編譯器/鏈接器標準部分并創建將按進程分組的新部分。工具鏈通常能夠創建多個零塊和初始化部分,如圖 3 所示。
【圖3 | 基于 MPU 的應用程序的 RAM 部分。]
創建命名的 RAM 部分
要按進程對數據進行分組,我們需要使用 EWARM #pragma 指令 default_variable_attributes 并將所有要在一個進程中分組的變量包裝在一起。
#pragma default_variable_attributes = @”.Process1”
// All variables that we want to be part of the section named “.Process1”。
#pragma default_variable_attributes =
如果您的應用程序包含在匯編語言文件中聲明的變量,那么您還需要確保匯編語言文件包含適當的匯編程序指令。
按塊對 RAM 進行分組
您的應用程序肯定會包含不一定與任何特定進程相關聯的代碼。在這種情況下,最好為這些模塊創建命名部分,然后將這些部分組合成一個公共代碼塊。然后,您將使用上述#pragma 指令創建不同的命名段,每個模塊一個,并使用鏈接器的 塊 指令(如下所示)對這些段進行分組。
define block COMMON_RAM_BLOCK with alignment = 4K, size = 4K
{
section .DRIVER_RAM,
section .COMMOM_RAM,
section .MATH_RAM,
section .STRING_RAM,
}
define block PROCESS_AI_RAM_BLOCK with alignment = 16K, size = 16K
{
section .AI_DRIVER_RAM, // Analog input driver
section .RTD_LIN_RAM, // RTD linearization
section .THERMOCOUPLE_LIN_RAM, // Thermocouple linearization
section .UNIT_CONVERSION_RAM, // Shared RAM with AO module
}
define block PROCESS_AO_RAM_BLOCK with alignment = 8K, size = 8K
{
section .AO_DRIVER_RAM, // Analog output driver
section .4_20MA_LIN_RAM, // 4-20 mA linearization
section .ACTUATOR_LIN_RAM, // Actuator linearization
section .UNIT_CONVERSION_RAM, // Shared RAM with AI module
}
define block SHARED_RAM_BLOCK with alignment = 2K, size = 2K
{
}
您會注意到 block 指令允許您指定內存塊的大小和對齊方式。為了將塊的起始地址放置在 MPU 進程表中,兩個值必須相同,這一點很重要。此外,每個塊所需的 RAM 量取決于應用程序。為了便于說明,我決定使用 16K、8K、4K 和 2K 字節。
定位 RAM 塊
我們現在可以使用兩個鏈接器指令將所有塊放置在 MCU 的可尋址空間中:區域和位置:
define region RAM = Mem:[from 0x20000000 size 64K];
place in RAM
{
block RAM_ALL with fixed order
{
block PROCESS_AI_RAM_BLOCK,
block PROCESS_AO_RAM_BLOCK,
block COMMON_RAM_BLOCK,
block SHARED_RAM_BLOCK
}
}
region 指令指定 MCU 的可尋址存儲器。如果您的 RAM 并非全部連續,則可能有不同的區域指令。
RAM 指令中的位置指定在 RAM 區域中定位塊。您會注意到我們需要將塊放入塊中以指定塊放置的順序。事實上,為了減少浪費的空間量,應該先使用較大的塊。
為每個任務創建 MPU 進程表
現在 RAM 按進程分組,您可以返回并編輯每個任務/進程的 MPU 表。但是,要做到這一點,編譯器必須知道塊的名稱,因此,您需要使用 #pragma section 指令,如下所示:
#pragma section = “COMMON_RAM_BLOCK”
#pragma section = “PROCESS_AI_RAM_BLOCK”
#pragma section = “PROCESS_AO_RAM_BLOCK”
#pragma section = “SHARED_RAM_BLOCK”
這兩個進程表現在可以如下所示(假設您沒有使用包含上一節中描述的每個任務回調的版本):
建議
以下是使用 Armv7-M MPU 時的一些建議。
在非特權模式下運行用戶代碼:
可以使用 MPU,但仍以特權模式運行所有應用程序代碼。當然,這意味著應用程序代碼將能夠更改 MPU 設置,因此會破壞擁有 MPU 的目的之一。最初以特權模式運行應用程序可能會更容易遷移應用程序代碼。但是,在某些時候,您的大部分應用程序代碼都需要在非特權模式下運行,因此您需要添加 SVC 處理程序。
將 PRIVDEFENA 設置為 1:
這允許特權代碼訪問完整的內存映射。理想情況下,您的大多數應用程序將在非特權模式下運行,只有 ISR 和 RTOS 將在特權模式下運行。此建議可避免為每個任務使用三個 MPU 區域,以授予特權代碼訪問任何 RAM 位置、任何代碼和任何外圍設備。將 PRIVDEFENA 設置為 1 的決定可能已經由 RTOS 供應商做出,您無法更改。
ISR 具有完全訪問權限:
每當識別到中斷并啟動 ISR 時,處理器就會切換到特權模式。由于 PRIVDEFENA 將設置為 1,因此 ISR 無論如何都可以訪問 I/O 位置的任何內存。您根本不想在進入 ISR 時重新配置 MPU,并在退出時重新配置它。因此,ISR 應該被視為系統級代碼,因此確實應該被允許具有完全訪問權限。
此外,ISR 應始終盡可能短,并簡單地向任務發出信號以執行中斷設備所需的大部分工作。當然,這假設 ISR 是內核感知的,并且任務有相當多的工作來處理中斷設備。例如,處理以太網數據包不應該在 ISR 級別完成。但是,可以直接在 ISR 中切換 LED 或更新脈沖寬度調制 (PWM) 定時器的占空比。
將 XN 位設置為 1:
如果您的應用程序代碼不希望在 RAM 外執行代碼,則應為所有 RAM 或外圍區域設置 RASR 寄存器的 eXecute Never 位。為外圍設備設置 XN 位可能看起來很奇怪,但它不會傷害并防止黑客試圖進入您的系統。
限制外圍設備對其進程的訪問:
您應該留出一個或多個 MPU 區域來限制進程只能訪問其自己的外圍設備。換句話說,如果一個進程管理 USB 端口,那么它應該只能訪問 USB 外圍設備或與 USB 控制器需求相關的外圍設備,例如 DMA。
限制 RTOS API:
系統設計人員需要確定哪些 RTOS API 應可用于應用程序代碼。具體來說,您想防止應用程序代碼在系統初始化后創建和刪除任務或其他 RTOS 對象(如信號量、隊列等)嗎?換句話說,RTOS 對象是否應該只在系統啟動時創建,而不是在運行時創建?如果是這樣,那么 SVC 處理程序查找表應該只包含您想要向應用程序公開的 API。然而,即使 ISR 在特權模式下運行并因此可以訪問任何 RTOS API,一個好的 RTOS 仍會阻止從 ISR 創建和刪除 RTOS 對象。
在 RTOS 空間中分配 RTOS 對象:
任務堆棧位于進程的內存空間內。然而,RTOS 對象(信號量、隊列、任務控制塊等)最好分配在內核空間中并通過引用進行訪問。換句話說,您不想在進程的內存空間中分配 RTOS 對象,因為這意味著應用程序代碼可以有意或無意地修改這些對象,而無需通過 RTOS API。
沒有全局堆:
將 MPU 設置為使用全局堆(即所有進程使用的堆)幾乎是不可能的,因此您應該盡可能避免使用這些堆。相反,如前所述,如果進程需要動態分配的內存(例如以太網幀緩沖區),則應允許進程特定的堆。
不要禁用中斷:
如果您的應用程序在非特權模式下運行,任何禁用中斷的嘗試都將被忽略。這樣做的問題是,您不會從 CPU 獲得中斷 未被 禁用的指示。
如果您的應用程序在非特權模式下運行并且您嘗試通過 NVIC 禁用中斷,則會觸發總線故障。
保護對代碼的訪問:
盡管 MPU 區域通常用于提供或限制對 RAM 和外圍設備的訪問,但如果您有空閑區域并且能夠按進程組織代碼(通過鏈接器命令),那么限制代碼對代碼的訪問可能很有用。這可以防止某些類型的安全攻擊,例如 Return-to-libc [2]。
減少進程間通信:
就像任務應該設計得盡可能獨立一樣,流程也應該遵循同樣的規則。因此,要么進程不相互通信,要么將進程間通信保持在最低限度。
如果您必須與其他進程通信,只需留出一個包含輸出和輸入緩沖區的共享區域。發送方將其數據放入輸出緩沖區,然后觸發中斷以喚醒接收進程。一旦數據被處理,響應(如果需要)可以放在發送者的緩沖區中,并且可以使用中斷來通知發送者。
確定遇到 MPU 故障時該怎么做:
理想情況下,在開發過程中檢測并糾正所有 MPU 故障。您應該計劃由于意外故障或錯誤或您的系統受到安全攻擊而在現場發生故障。在大多數情況下,建議對每個任務或每個進程都有一個受控的關閉順序。是否重新啟動有問題的任務、進程內的所有任務或整個系統取決于故障的嚴重程度。
有辦法記錄和報告故障:
理想情況下,您有辦法記錄(可能記錄到文件系統)并顯示故障原因,以允許開發人員解決問題。
結論
內存保護單元 (MPU) 是將對內存和外圍設備的訪問限制為僅需要訪問這些資源的代碼的硬件。如果任務試圖訪問其分配空間之外的內存位置或外圍設備,則會觸發 CPU 異常,并且根據應用程序,必須采取糾正措施。
Cortex-M MCU 中的 MPU 是一個相當簡單的設備,并且相對容易配置。然而,使用 MPU 的復雜性更傾向于按進程分配存儲(主要是 RAM)以及創建將在上下文切換期間加載到 MPU 中的 MPU 進程表。
最后,我提供了一個建議列表,這些建議可以更好地在您的應用程序中使用 MPU。
單獨的軟件無法阻止對未分配給 RTOS 環境中任務的內存或外圍設備的訪問。您需要硬件來實現這一點,而 MPU 是目前 Cortex-M (Armv7-M) 上唯一可以做到這一點的機制。
遷移應用程序以使用 MPU 是一個相當簡單但乏味的過程。添加 MPU 也會給您的應用程序帶來開銷:在上下文切換期間您需要加載額外的寄存器,并且用戶代碼應該在非特權模式下運行以避免此類代碼更改 MPU 設置。
審核編輯:郭婷
-
mcu
+關注
關注
146文章
17123瀏覽量
350992 -
寄存器
+關注
關注
31文章
5336瀏覽量
120231 -
MPU
+關注
關注
0文章
357瀏覽量
48775
發布評論請先 登錄
相關推薦
評論