天天看點

ARM MMU 了解(基于ARM920T)

MMU簡介

嵌入式系統中,存儲系統差别很大,可包含多種類型的存儲器件,如FLASH,SRAM,SDRAM,ROM等,這些 不同類型的存儲器件速度和寬度等各不相同;在通路存儲單元時,可能采取平闆式的位址映射機制對其操作,或需要使用虛拟位址對其進行讀寫;系統中,需引入存 儲保護機制,增強系統的安全性。為适應如此複雜的存儲體系要求,ARM處理器中引入了存儲管理單元來管理存儲系統。

在ARM存儲系統中,使用MMU實作虛拟位址到實際實體位址的映射。為何要實作這種映射?首先就要從一個嵌入式系統的基本構成和運作方式着手。系統上電 時,處理器的程式指針從0x0(或者是由0Xffff_0000處高端啟動)處啟動,順序執行程式,在程式指針(PC)啟動位址,屬于非易失性存儲器空間 範圍,如ROM、FLASH等。然而與上百兆的嵌入式處理器相比,FLASH、ROM等存儲器響應速度慢,已成為提高系統性能的一個瓶頸。而SDRAM具 有很高的響應速度,為何不使用SDRAM來執行程式呢?為了提高系統整體速度,可以這樣設想,利用FLASH、ROM對系統進行配置,把真正的應用程式下 載到SDRAM中運作,這樣就可以提高系統的性能。然而這種想法又遇到了另外一個問題,當ARM處理器響應異常事件時,程式指針将要跳轉到一個确定的位 置,假設發生了IRQ中斷,PC将指向0x18(如果為高端啟動,則相應指向0vxffff_0018處),而此時0x18處仍為非易失性存儲器所占據的 位置,則程式的執行還是有一部分要在FLASH或者ROM中來執行的。那麼我們可不可以使程式完全都SDRAM中運作那?答案是肯定的,這就引入了 MMU,利用MMU,可把SDRAM的位址完全映射到0x0起始的一片連續位址空間,而把原來占據這片空間的FLASH或者ROM映射到其它不相沖突的存 儲空間位置。例如,FLASH的位址從0x0000_0000-0x00ff_ffff,而SDRAM的位址範圍是 0x3000_0000-0x31ff_ffff,則可把SDRAM位址映射為0x0000_0000-0x1fff_ffff而FLASH的位址可以映 射到0x9000_0000-0x90ff_ffff(此處位址空間為空閑,未被占用)。映射完成後,如果處理器發生異常,假設依然為IRQ中斷,PC指 針指向0x18處的位址,而這個時候PC實際上是從位于實體位址的0x3000_0018處讀取指令。通過MMU的映射,則可實作程式完全運作在 SDRAM之中。

在實際的應用中,可能會把兩片不連續的實體位址空間配置設定給SDRAM。而在作業系統中,習慣于把SDRAM的空間連續起來,友善記憶體管理,且應用程式申請 大塊的記憶體時,作業系統核心也可友善地配置設定。通過MMU可實作不連續的實體位址空間映射為連續的虛拟位址空間。

作業系統核心或者一些比較關鍵的代碼,一般是不希望被使用者應用程式所通路的。通過MMU可以控制位址空間的通路權限,進而保護這些代碼不被破壞。

MMU的實作過程,實際上就是一個查表映射的過程。建立頁表(translate table)是實作MMU功能不可 缺少的一步。頁表是位于系統的記憶體中,頁表的每一項對應于一個虛拟位址到實體位址的映射。每一項的長度即是一個字的長度(在ARM中,一個字的長度被定義 為4位元組)。頁表項除完成虛拟位址到實體位址的映射功能之外,還定義了通路權限和緩沖特性等。

MMU的映射分為兩種,一級頁表的變換和二級頁表變換。兩者的不同之處就是所實作的變換位址空間大小不同。一級頁表變換支援1M大小的存儲空間的映射,而 二級可以支援64KB、4KB和1KB大小位址空間的映射。

要實作從虛拟位址到實體位址的映射,必然會遇到一個問題,如何找到這個頁表。對于表的查找,要知道這個表的基位址和偏移位址,在具有MMU功能的處理器 中,內建了一個被稱為CP15的協處理器,該協處理器的C2寄存器中用于儲存頁表的基位址,下面以一級頁表變換為例說明MMU實作位址變換的過程。

如圖1.1所示,當處理器通路一個虛拟位址時,該虛拟位址的[31:20]作為偏移位址與頁基位址結合(基位址必須是64KB對齊的,是以基位址的 [13:0]位都為0),得到一個32位的頁表項位址(因為頁表項為4位元組對齊,[1:0]兩位為0)。通過這個頁表項位址可以檢索到該頁表項。頁表項的 格式如下:

表 1.3

表 1.4

查找到頁表項後,根據頁表項的通路特性(緩沖以及是否允許通路等)協處理器決定是否允許通路。如不允許通路,則協處理器向CPU報告出錯資訊;反之,由頁 表項的[31:20]位與虛拟位址的[19:0]一起組成實際的實體位址,實作從虛拟位址到實體位址的映射。

對于實際程式設計工作而言,主要是确定如何編寫頁表中的内容并如何确定頁表項位址。現舉例如下:

假設實體位址為0x36B0_0000~0x36Bf_ffff(1M空間)的一塊連續空間需映射為0x0100_0000~0x010f_ffff的一 塊連續空間:

1.确定頁表項中的内容:把實體位址的基位址作為頁表項的高12位(31bit~21bit),填寫通路屬性。假設可以讀寫,可以讀緩存、寫緩沖,這樣該 頁表項内容為0x36B0_0C00E;

2.确定頁表基位址,填寫頁表基位址到CP15寄存器的C2中。頁表的基位址要為64KB對齊;

3.計算出偏移位址,把内容填寫到頁表項位址中。頁表項位址=頁表基位址+(實體位址基位址>>18),如頁表基位址為 0xA100_0000,那麼,頁表項位址=0xA100_0DAC;

4.将頁表項數值寫到對應的頁表項位址中。上例中,需要向位址0xA100_0DAC中寫入0x36B0_0C00E。

ARM920T的MMU與Cache

Cache是高性能CPU解決總線通路速度瓶頸的方法,然而它的使用卻是需要權衡的,因為緩存本身的動作,如塊拷貝和替換等,也是很消耗CPU時間的。 MMU的重要性勿庸置疑,ARM920T(和ARM720T)內建了MMU是其最大的賣點;有了MMU,進階的作業系統(虛拟位址空間,平面位址,程序保 護等)才得以實作。二者都挺複雜,并且在920T中又高度耦合,互相配合操作,是以需要結合起來研究。同時,二者的操作對象都是記憶體,記憶體的使用是使用 MMU/Cache的關鍵。另外,MMU和Cache的控制寄存器不占用位址空間,CP15是操縱MMU/Cache的唯一途徑。

Cache/Write Buffer的功能

Cache通過預測CPU即将要通路的記憶體位址(一般都是順序的),預先讀取大塊記憶體供CPU通路,來減少後續的記憶體總線上的讀寫操作,以提高速度。然 而,如果程式中長跳轉的次數很多,Cache的命中率就會顯著降低,随之而來,大量的替換操作發生,于是,過多的記憶體操作反而降低了程式的性能。

ARM920T内部采用哈佛結構,将内部指令總線和資料總線分開,分别連接配接到ICache和DCache,再通過AMBA總線接口連接配接到ASB總線上去訪 問記憶體。Cache由Line組成,Line是Cache進行塊讀取和替換的機關。

Writer Buffer是和DCache相逆過程的一塊硬體,目的也是通過減少memory bus的 通路來提高性能。

MMU的功能

在記憶體中維護一張或幾張表,就看你怎麼給記憶體劃分page和section了。通過CP15指定好轉換表的位置,920T的硬體會自動将轉換表的一部分讀 到TLB中。CPU每次進行記憶體讀寫時,發出虛拟位址,參照TLB中的轉換表轉換到實體位址,并讀取相應entry中的資訊,以決定是否可以有權限讀寫和 緩存。

mmugen這個工具就是幫你構造這個表的,省的自己寫程式了。

操作MMU,實際上就是如何配置設定和使用你的記憶體,并記錄在translationtable裡。

ARM920T中,MMU的每條entry包括Cachable和Buffable位來指定相應的記憶體是否可以用Cache緩存。此處就是MMU與 Cache的互動作用處。

實際上,MMU和Cache的使用是作業系統設計者根據系統軟硬體配置而考慮的事情。作業系統針對配置設定給應用程式的位址空間作記憶體保護和緩存優化。在沒有 作業系統的情況下,就需要我們自己來掌控它們了。其中,主要是合理配置設定記憶體。

我認為,以下幾點需要着重考慮:

1) 安全第一! -- 避免MMU和Cache的副作用。

當你在無OS的裸機上開發程式時,初始化運作環境的代碼很重要,比如:各種模式堆棧指針的初始化;将代碼和RW data 從ROM拷貝到RAM;初始化.bss段(zero initialized)空間等。此時會有大量的記憶體操作,如果你enable了 Cache,那麼在拷貝完代碼之後,一定要invalidate ICache和flush DCache。否則将會出現緩存中的代 碼或資料與記憶體中的不一緻,程式跑飛。

另外,有時候我們需要自己作loader來直接運作ELF檔案,情況也是一樣,拷貝完代碼後一定要重新整理Cache,以免不測。

還有,對硬體的操作要小心。很多寄存器值都是被硬體改變的,讀寫時,要保證确實通路到它的位址。首先,在C語言代碼中聲明為volatile變量,以防止 記憶體讀寫被編譯器優化掉;另外,設定好TLB,使得寄存器映射的位址空間不被緩存。

總之,緩存和記憶體中代碼的不一緻,是一定要避免的。

2) 弄巧成拙! -- 隻對頻繁通路的位址空間進行Cache優化。

我們很清楚自己的程式中,那裡有大量的運算,哪裡有無數的循環或遞歸,而這正是Cache的用武之地,我們将這些空間進行緩存将大大提高運作速度。但是, 很多函數或子程式往往僅僅運作很少幾次,若是對它們也緩存,隻會撿了芝麻丢了西瓜,造成不必要的緩存和替換操作,反而增加了系統負擔,降低了整體性能。

3) 斷點哪兒去了? -- 如何調試“加速”了的代碼?

據我所知,一般,debugger都是通過掃描位址總線,在斷點處暫停CPU。ARM9TDMI中內建的JTAG調試口,也是這樣。

當我們調試使用Cache的代碼時,将會出現問題。比如:CPU通路某斷點所在位址之前的位址時,發生緩存操作,斷點處代碼被提前讀入Cache,此時地 址總線上出現了斷點位址,CPU被debugger暫停,并且斷點之後的指令也被Cache緩存。于是,當你從斷點處step時,程式卻停不了了,因為地 址總線上不再出現斷點之後的下一個位址了。

再舉個例子:

int i,a;

for (i=0; i<100; i++) {

-> a++;

}

當位址總線上第一次出現斷點位址時,CPU暫停;之後,就再也不會停了。因為,之後CPU會從cache中直接去代碼了。(當然,後來,Cache的代碼 有可能會被替換掉,斷點又可到達。) 所幸的是,我用的debugger提供JTAG Monitor,允許斷點跟蹤使用cache 的程式。

全部參考來源于ARM的手冊,建議詳細閱讀1)的PartB及其在2)中的具體實作。

1) DDI0100E_ARM_ARM.pdf (ARM Architecture Reference Manual)

2) DDI0151C_920T_TRM.pdf (ARM920T Technical Reference Manual)