1. 程式人生 > >S3C2440的記憶體管理單元MMU學習筆記

S3C2440的記憶體管理單元MMU學習筆記

1.MMU簡介

   MMU(Memory Management Unit),記憶體管理單元,主要職責: 將虛擬地址對映為實體地址,提供硬體機制的記憶體訪問許可權檢查 MMU使得每個使用者程序擁有自己獨立的地址空間,並通過記憶體訪問許可權的檢查保護每個程序所用的記憶體不被其他程序破壞。 2.基本概念 1)地址的分類
一個程式在執行之前,沒有必要全部裝入記憶體,僅需要將那些要執行的部分先裝入記憶體,其餘部分在用到時從磁碟載入,當記憶體不足時,再將暫時不用的部分調出到磁碟。 這使得大程式可以在較小的記憶體空間中執行,也使得記憶體中可以同時裝入更多的程式併發執行,這樣的儲存器一般稱為虛擬儲存器
虛擬地址最終需要轉換為實體地址才能讀寫實際的資料,通過將虛擬地址空間和物理空間劃分為同樣大小的空間(段或頁),然後兩個空間建立對映關係 由於虛擬地址空間遠大於實體地址,可能多塊虛擬地址空間對映到同一塊實體地址空間,或者有些虛擬地址空間沒有對映到具體的實體地址空間上去(使用到時再對映)。
 
ARM cpu地址轉換涉及三種地址:虛擬地址(VA,Virtual Address)、變換後的虛擬地址(MVA,Modified Virtual Address)、實體地址(PA,Physical Address) 2)VA是CPU使用的地址,MVA是MMU、Caches使用的,他們的對映關係:


3)沒有啟動 MMU 時,CPU核心,cache,MMU,外設等所有部件使用的都是實體地址啟動MMU後,CPU核心對外發出虛擬地址VA;VA被轉換為MVA供cache,MMU使用,在這裡MVA被轉換成PA;最後使用PA讀取實際裝置

①CPU核心看到和用到的只是虛擬地址VA,至於VA如果去對應實體地址PA,CPU核心不理會
②caches和MMU看不到VA,他們利用MVA轉換得到PA
③實際裝置看不到VA、MVA,讀寫它們使用的是實體地址PA
3.地址轉換中的基本概念 地址的轉換就像是數學中對映,利用函式公式,多對1,就是多個虛擬地址對應同一個實體地址。 這裡地址轉換用的是頁表的方式。 (1)頁表    頁表是由一個個表項(Entry,又稱作描述符)組成的物理表,每個頁表項或是一個物理頁(一塊實體記憶體,大小為1k,4k不等)的起始地址,或是一個二級頁表的地址(當是一個二級頁表的地址時,又可以稱該頁表為頁描述符)。
所有的頁表都用實體地址訪問 (2)表項(描述符)的分類 依據描述符儲存的內容,可以分為兩類。
  • 一類儲存的是直接的物理頁或段的起始地址,如段描述符、大頁、小頁、極小頁描述符。
  • 另一類儲存的是二級頁表的實體地址,如粗頁表描述符,細頁表描述符。
(3)一級對映與二級對映
  • 一級對映,是指以段(Section,大小1MB)的方式進行轉換。
  • 二級對映,是以頁的方式進行轉換。ARM頁大小由三種:1k,4k,64k。
4.地址轉換的過程  (1)CPU發出一個VA,是怎麼轉換為MVA的,這在上圖可以看到,是通過一個硬體電路轉換的。在ARM9裡面,如果VA<32M,利用程序標識號PID轉換得到MVA。過程如下:
if(VA < 32M)
     MVA = VA | ( PID<<25 )
else
     MVA = VA
只是上面的過程是由硬體實現的。

  這樣為VA進行了一級對映,為什麼呢?在linux系統裡,每個程序的地址空間0-4G,0-3G是程序獨有的,稱為使用者空間,3G-4G是系統的,稱為核心空間,所有程序共享。如果兩個程序所用的VA有重疊,在切換程序時,為了把重疊的VA對映到不同的PA上,需要重建頁表、使無效caches和TLBS。使用了MVA,使程序在VA相同的情況下,使用不同的MVA,進而PA也不同。這就是在VA與PA之間加上一次到MVA的對映的意義。

(2)MVA到PA的轉換 上面說了,虛擬地址到實體地址的轉換有兩種方式:公式對映+頁錶轉換。 概念上面也介紹過了。這裡就進行實際的轉換說明。 首先,給定一個MVA,要進行轉換,先要找到一級頁表,怎麼找? 有一個暫存器, TTB暫存器,頁表基地址暫存器 ,負責儲存一級頁表的基地址,當然是實體地址 TTB暫存器是整合在CP15協處理器 中的:

TTB的格式:


因此,一級頁表的地址是16k對齊的,因為[13:0]要求為0.

3)虛擬地址到實體地址的轉換過程
 arm cpu使用頁表來進行轉換,頁表由一個個條目組成,每個條目儲存一段虛擬地址對應的實體地址及訪問許可權,或者下一級頁表的地址
 S3C2440最多會用到兩級頁表,以段(Section,1M)的方式進行轉換時只用到一級頁表,以頁(Page)的方式進行轉換時用到兩級頁表。

粗頁表描述符、細頁表描述符,它們儲存二級頁表的實體地址。-> 頁的大小有3種:大頁(64KB),小頁(4KB),極小頁(1KB)。->條目也稱為描述符,有:段描述符、大頁描述符、小頁描述符、極小頁描述符-儲存段、大頁、小頁、極小頁的起始實體地址;

下圖為S3C2440的地址轉換圖


TTB base代表一級頁表的地址,將它寫入協處理器CP15的暫存器C2(稱為頁表基址暫存器)即可,一級頁表的地址是16K對齊,使用[31:14]儲存頁表基址,[13:0]為0。
 一級頁表使用4096個描述符來表示4GB空間,每個描述符對應1MB的虛擬地址,儲存它對應的1MB物理空間的起始地址,或者儲存下一級頁表[二級頁表]的地址。使用MVA[31:20]來索引一級頁表(20-31一共12位,2^12=4096,所以是4096個描述符),得到一個描述符,每個描述符佔4個位元組。

一級頁表描述符格式如下:


一級頁表描述符 最低兩位:
 0b00:無效

0b10:段(Section)
        [31:20]為段基址,、此描述符低20位填充0後就是一塊1MB實體地址空間的起始地址。MVA[19:0],用來在這1MB空間中定址。描述符的位[31:20]和MVA[19:0]構成了這個虛擬地址MVA對應的實體地址。
以段的方式進行對映時,虛擬地址MVA到實體地址PA的轉換過程如下:
①頁表基址暫存器位[31:14]和MVA[31:20]組成一個低兩位為0的32位地址,MMU利用這個地址找到段描述符

②取出段描述符的位[31:20](段基址),它和MVA[19:0]組成一個32位的實體地址(這就是MVA對應的PA)


                                                段地址轉換過程

0b01:粗頁表(Coarse page table)

        [31:10]為粗頁表基址,此描述符低10位填充0後就是一個二級頁表的實體地址,二級頁表含256個條目(使用[9:2],2^8=256個),稱為粗頁表(Coarse page table)。其中每個條目表示4KB大小的實體地址空間,一個粗頁表表示1MB實體地址。

0b11:細頁表(Fine page table)

        [31:12]為細頁表基址(Fine page table base address),此描述符的低12位填充0後,就是一個二級頁表的實體地址。此二級頁表含1024個條目(使用[11:2],10位),其中每個條目表示大小1kb的實體地址空間,一個細頁表表示1MB實體地址空間。

以大頁(64KB),小頁(4KB)或極小頁(1KB)進行地址對映時,需要用到二級頁表,二級頁表有粗頁表、細頁表兩種,二級頁表描述符格式如下:


                                        二級頁表描述符

最低兩位:
0b00:無效
0b01:大頁描述符
        位[31:16]為大頁基址,此描述符的低16位填充0後就是一塊64KB實體地址空間的起始地址粗頁表中的每個條目只能表示4KB物理空間,如果大頁描述符儲存在粗頁表中,則連續16個條目都儲存同一個大頁描述符。類似的,細頁表中每個條目只能表示1KB的物理空間,如果大頁描述符儲存在細頁表中,則連續64個條目都儲存同一個大頁描述符。
下面以儲存在粗頁表中的大頁描述符為例,說明地址轉化那過程
①頁表基址暫存器[31:14]和MVA[31:20]組成一個低兩位為0的32位地址,MMU利用這個地址找到粗頁表描述符
②取出粗頁表描述符的[31:10](即粗頁表基址),它和MVA[19:12]組成一個低兩位為0的32位實體地址,通過這個地址找到大頁描述符
③取出大頁描述符的[31:16](即大頁基址),它和MVA[15:0]組成一個32位的實體地址,即MVA對應的PA

步驟②和③中,用於在粗頁表中索引的MVA[19:12]、用於在大頁內定址的MVA[15:0]有重合的位[15:12],當位[15:12]從0b0000變化到0b1111時,步驟②得到的大頁描述符相同,所以粗頁表中有連續16個條目儲存同一個大頁描述符


大頁的地址轉換過程(大頁描述符儲存在粗頁表中)

0b10:小頁描述符

[31:12]為小頁基址(Small page base address),此描述符的低12位填充0後就是一塊4kb([11:0],一共12位,2^12=4096)實體地址空間的起始地址。粗頁表中每個條目表示4kb的物理空間,如果小頁描述符儲存在粗頁表中,則只需要用一個條目來儲存一個小頁描述符。類似的,細頁表中每個條目只能表示1kb的物理空間,如果小頁儲存在細頁表中,則連續4個條目都儲存同一個小頁描述符。

下面以儲存在粗頁表中的小頁描述符為例,說明地址轉換過程:

①頁表基址[31:14]和MVA[31:20]組成一個低兩位為0的32位地址,MMU利用這個地址找到粗頁表描述符

②取出粗頁表描述符[31:10](即粗頁表基址),它和MVA[19:12]組成一個低兩位為0的32位實體地址,用這個地址找到小頁描述符

③取出小頁描述符的位[31:12](即小頁基址),它和MVA[11:0]組成一個32位實體地址(即MVA對應的PA)

小頁描述符儲存在細頁表中,地址轉換過程和上面類似。小頁的地址轉換過程(小頁描述符儲存在粗頁表中)

0b11:極小頁描述符

        [31:10]為極小頁基址(Tiny page base address),此描述符的低10位填充0後就是一塊1KB實體地址空間的起始地址。極小頁描述符只能儲存在細頁表中,用一個條目來儲存一個極小頁描述符。

下面是極小頁的地址轉換過程:

①頁表基址暫存器[31:14]和MVA[31:20]組成一個低兩位為0的32位地址,MMU通過這個地址找到細頁表描述符

②取出細頁表描述符[31:12](即細頁表基址),它和MVA[19:10]組成一個低兩位為0的32位實體地址,通過這個地址即可找到極小頁描述符

③取出極小頁描述符[31:10](即極小頁基址),它和MVA[9:0]組成一個32位的實體地址(即MVA對應的PA)


極小頁的地址轉換過程(極小頁描述符儲存在粗頁表中)


從段、大頁、小頁、極小頁的地址轉換過程可知:

①以段進行對映時,通過MVA[31:20]結合頁表得到一段(1MB)的起始實體地址,MVA[19:0]用來在段中定址

②以大頁進行對映時,通過MVA[31:16]結合二級頁表得到一個大頁(64KB)的起始實體地址,MVA[15:0]用來在小頁中定址

③以小頁進行對映時,通過MVA[31:12]結合二級頁表得到一個小頁(4KB)的起始實體地址,MVA[11:0]用來在小頁中定址

④以極小頁進行對映時,通過MVA[31:10]結合二級頁表得到一個極小頁(1KB)的起始實體地址,MVA[9:0]用來在極小頁中定址

 

2、記憶體的訪問許可權檢查

它決定一塊記憶體是否允許讀、是否允許寫。這由CP15暫存器C3(域訪問控制)、描述符的域(Domain)、CP15暫存器C1的R/S/A位、描述符的AP位共同決定。“域”決定是否對某塊記憶體進行許可權檢查,“AP”決定如何對某塊內容進行許可權檢查。

S3C2440有16個域,CP15暫存器C3(域訪問控制暫存器)中每兩位對應一個域(一共32位),用來表示這個域是否進行許可權檢查。每兩位資料的含義:

00:無訪問許可權(任何訪問都將導致“Domain fault”異常)

01:客戶模式(使用段描述符、頁描述符進行許可權檢查)

10:保留(保留,目前相當於“無訪問許可權”)

11:管理模式(不進行許可權檢查,允許任何訪問)

描述符的域Domain佔用4位,用來表示記憶體屬於0-15,哪一個域。

例如:

①段描述符中的“Domain”為0b0010,表示1MB記憶體屬於域2,如果域訪問控制暫存器的[5:4]等於0b00,則訪問這1MB空間都會產生“Domain fault”異常,如果等於0b01,則使用描述符中的“Ap”位進行許可權檢查。

②粗頁表中的“Domain”為0b1010,表示1MB記憶體屬於域10,如果域訪問控制暫存器的[21:20]等於0b01,則使用二級頁表中的大頁/小頁描述符中的"ap3"、"ap2"、"ap1"、"ap0"位進行許可權檢查,如果等於0b11,則允許任何訪問,不進行許可權檢查。

如下圖:


                                一級頁表描述符


                                二級頁表描述符

AP、ap3、ap2、ap1、ap0結合CP15暫存器C1的R/S位,決定如何進行訪問檢查。
段描述符中AP控制整個段(1MB)訪問許可權;大頁描述符每個apx(0-3)控制一個大頁(64KB)中1/4記憶體的訪問許可權,即ap3對應大頁高階的16KB,ap0對應大頁低端的16KB;小頁描述符與大頁描述符類似,每個apx(0-3)控制一個小頁(4KB)的1/4記憶體的訪問許可權;極小頁中的ap控制整個極小頁(1KB)的訪問許可權。

AP/S/R對照表
AP S R 特權模式 使用者模式 說明
00 0 0 無訪問許可權 無訪問許可權 任何訪問將產生“Permission fault”異常 
00 1 0 只讀 無訪問許可權 在超級許可權下可以進行讀操作 
00 0 1 只讀 只讀 任何寫操作將產生”Permission fault“異常 
00 1 1 保留 - -
01 x x 讀/寫 無訪問許可權 只允許在超級模式下訪問
10 x x 讀/寫 只讀 在使用者模式下進行寫操作將產生"Permission fault"異常
11 x x 讀/寫 讀/寫 在所有模式下允許任何訪問 
xx 1 1 保留 - -



 
 3、TLB的作用

從MVA到PA的轉換需要訪問多次記憶體,大大降低了CPU的效能,有沒有辦法改進呢?

程式執行過程中,用到的指令和資料的地址往往集中在一個很小的範圍內,其中的地址、資料經常使用,這是程式訪問的區域性性。

由此,通過使用一個高速、容量相對較小的儲存器來儲存近期用到的頁表條目(段、大頁、小頁、極小頁描述符),避免每次地址轉換都到主存中查詢,這樣就大幅提高效能。這個儲存器用來幫助快速地進行地址轉換,成為轉譯查詢快取(Translation Lookaside Buffers, TLB)

當CPU發出一個虛擬地址時,MMU首先訪問TLB。如果TLB中含有能轉換這個虛擬地址的描述符,則直接利用此描述符進行地址轉換和許可權檢查,否則MMU訪問頁表找到描述符後再進行地址轉換和許可權檢查,並將這個描述符填入TLB中,下次再使用這個虛擬地址時就直接使用TLB用的描述符。

使用TLB需要保證TLB中的內容與頁表一致,在啟動MMU之前,頁表中的內容發生變化後,尤其要注意。一般的做法是在啟動MMU之前使整個TLB無效,改變頁表時,使所涉及的虛擬地址對應的TLB中條目無效。

 

4、Cache的作用

同樣基於程式訪問的區域性性,在主存和CPU通用暫存器之間設定一個高速的、容量相對較小的儲存器,把正在執行的指令地址附近的一部分指令或資料從主存調入這個儲存器,供CPU在一段時間內使用,對提高程式的執行速度有很大作用。這個cache一般稱為快取記憶體。

①寫穿式(Write Through)

任一CPU發出寫訊號送到Cache的同時,也寫入主存,保證主存的資料同步更新。優點是操作簡單,但由於主存速度慢,降低了系統的寫速度並佔用了匯流排的時間。

②回寫式(Write Back)

資料一般只寫到Cache,這樣可能出現Cache中的資料得到更新而主存中的資料不變(資料陳舊)的情況。此時可在Cache中設一個標誌地址及資料陳舊的資訊,只有當Cache中的資料被換出或強制進行”清空“操作時,才將原更新的資料寫入主存響應的單元中,保證了Cache和主存中資料一致。

 

Cache有以下兩個操作:

①”清空“(clean):把Cache或Write buffer中已經髒的(修改過,但未寫入主存)資料寫入主存

②”使無效“(Invalidate):使之不能再使用,並不將髒的資料寫入主存。

 

S2C2440內建了指令Cache(ICaches)、資料Cache(DCaches)、寫快取(Write buffer),需要用到描述符中的C位(Ctt)和B位(Btt)

1)指令Cache(ICaches)

系統剛上電或復位時,ICaches中的內容是無效的,並且ICaches功能關閉。往Icr位(CP15協處理器中暫存器1的第12位)寫1可以啟動ICaches,寫0停止ICaches

ICaches一般在MMU開啟後使用,此時描述符的C位用來表示一段記憶體是否可以被Cache。若Ctt=1,允許Cache,否則不允許。如果MMU沒有開啟,ICaches也可以被使用,此時CPU讀取指令時所涉及的記憶體都被當做允許Cache

ICaches關閉時,CPU每次取指都要讀取主存,效能低,所以通常儘早啟動ICaches

ICaches開啟後,CPU每次取指時都會先在ICaches中檢視是否能找到所用指令,而不管Ctt是0還是1。如果找到成為Cache命中,找不到稱為Cache丟失,ICaches被開啟後,CPU的取指有如下三種情況:

①Cache命中且Ctt為1時,從ICaches中取指,返回CPU

②Cache丟失且Ctt為1時,CPU從主存中取指,並且把指令快取到Cache中

③Ctt為0時,CPU從主存中取指

2)資料Cache(DCaches)

與ICaches相似,系統剛上電或復位時,DCaches中的內容無效,並且DCaches功能關閉,Write buffer中的內容也是被廢棄不用的。往Ccr位(CP15協處理器 中暫存器1的第二位)寫1啟動DCaches,寫0停止DCaches。Write buffer和DCaches緊密結合,額米有專門的控制來開啟和停止它

與ICaches不同,DCaches功能必須在MMU開啟之後才能被使用。

DCaches被關閉時,CPU每次都去記憶體取資料。

DCaches被開啟後,CPU每次讀寫資料時都會先在DCaches中檢視是否能找到所要的資料,不管Ctt是0還是1,找到了成為Cache命中,找不到成為Cache丟失。

通過下表可知DCaches和Write buffer在Ccr,Ctt,Btt各種取值下,如何工作,Ctt and Ccr 意為 Ctt與Ccr進行邏輯與後的值

 

Ctt and Ccr Btt DCaches、Write buffer 和主存的訪問方式 
0 0 Non-cached,non-buffered(NCNB)

讀寫資料時都是直接操作主存,並且可以被外設中止;

寫資料時不使用Write buffer,CPU會等待寫操作完成;

不會出現Cache命中
 
0 1 Non-Cached buffered(NCB)

讀資料時都是直接操作主存;

不會出現Cache命中;

寫資料時,資料線存入Write buffer,並在隨後寫入主存;

資料存入Write buffer後,CPU立即繼續執行;

讀資料時,可以被外設中止;

寫資料時,無法被外設中止
 
1 0 Cached,write-through(寫通)mode

讀資料時,如果Cache命中則從Cache中返回資料,不讀取主存;

讀資料時,如果Cache丟失則從讀主存中返回資料,並導致“linefill”的動作;

寫資料時,資料先存入Write buffer,並在隨後寫入主存;

資料存入Write buffer後,CPU立即繼續執行;

寫資料時,如果Cache命中則新資料也寫入Cache中;

寫資料時,無法被外設中止
 
1 1 Cached,write-back(寫回) mode

讀資料時,如果Cache命中則從Cache中返回資料,不讀取主存;

讀資料時,如果Cache丟失則從讀主存中返回資料,並導致“linefile”的動作;

寫資料時,如果Cache丟失則將資料先存入Write buffer,儲存完畢後CPu立即繼續執行,這些資料在隨後寫入主存;

寫資料時,如果Cache命中則在Cache中更新資料,並設定這些資料為”髒的“,但是不會寫入主存;

無論Cache命中與否,寫資料都無法被外設中止
 

 使用Cache時需要保證Cache、Write buffer的內容和主存內容一致,保證下面兩個原則:

①清空DCaches,使主存資料得到更新

②使無效ICaches,使CPU取指時重新讀取主存

在實際編寫程式時,要注意如下幾點:

①開啟MMU前,十五小ICaches,DCaches和Write buffer

②關閉MMU前,清空ICaches、DCaches,即將”髒“資料寫到主存上

③如果程式碼有變,使無效ICaches,這樣CPU取指時會從新讀取主存

④使用DMA操作可以被Cache的記憶體時,將記憶體的資料傳送出去時,要清空Cache;將記憶體的資料讀入時,要使無效Cache

⑤改變頁表中地址對映關係時也要慎重考慮

⑥開啟ICaches或DCaches時,要考慮ICaches或DCaches中的內容是否與主存保持一致

⑦對於I/O地址空間,不使用Cache和Write buffer

 

5、S3C2440 MMU、TLB、Cache的控制指令

S3C2440除了ARM920T的CPU核心外,還有若干個協處理器,用來幫助主CPu完成一些特殊功能。對MMU、TLB、Cache等的操作涉及到協處理器。

<MCR|MRC>{條件} 協處理器編碼,協處理器操作碼1,目的暫存器,源暫存器1,源暫存器2,協處理器操作碼2

<MCR|MRC> {cond} p#,<expression1>,Rd,cn,cm{,<expression2>}

MRC        //從協處理器獲得資料,傳給ARM920T CPU核心暫存器

MCR        //資料從ARM920T CPU核心暫存器傳給協處理器

{cond}        //執行條件,省略時表示無條件執行

p#        //協處理器序號

<expression1>        //一個常數

Rd        //ARM920T CPU核心的暫存器

cn和cm        //協處理器中的暫存器

<expression2>        //一個常數

其中,<expression1>、cn、cm、<expression2>僅供協處理器使用,它們的作用如何取決於具體的協處理器

 

二、MMU使用例項:地址對映

這個例項將開啟MMU,並將虛擬地址0xA0000000-0xA0100000對映到實體地址0x56000000-0x56100000(GPBCON實體地址為0x56000010,GPBDAT實體地址為0x56000014),來驅動LED。

將虛擬地址0xB0000000-0xB3FFFFFF對映到實體地址0x30000000-0x33FFFFFF,在連線程式時,將一部分程式碼的執行地址指定為0xB0004000.

這個程式只使用一級頁表,以段的方式進行地址對映,32位CPU虛擬地址空間達到4G,一級頁表使用4096個描述符來表示4G空間(每個描述符對應1MB),每個描述符佔4位元組,所以一級頁表佔16KB。這個程式使用SDRAM的開始16KB存放一級頁表,所以剩下的記憶體開始地址就為0x30004000,這個地址最終會對應虛擬地址0xB0004000(所以程式碼執行地址為0xB0004000)

程式分為兩部分:第一部分的執行地址為0,它用來初始化SDRAM,複製第二部分的程式碼到SDRAM中(存放在0x30004000)、設定頁表、啟動MMU,最後跳到SDRAM中(地址0xB0004000),第二部分執行地址設為0xB0004000,用來驅動LED

先看連線檔案mmu.lds


SECTIONS {  
  firtst    0x00000000 : { head.o init.o }  
  second    0xB0004000 : AT(2048) { leds.o }  
}  
程式分兩個段:first和second。first由head.o和init.o組成,載入和執行地址都是0,second由leds.o組成,載入地址為2048,重定位地址為0xB0004000。  
   
   
@*************************************************************************  
@ File:head.S  
@ 功能:設定SDRAM,將第二部分程式碼複製到SDRAM,設定頁表,啟動MMU,  
@       然後跳到SDRAM繼續執行  
@*************************************************************************        
.text  
.global _start  
_start:  
    ldr sp, =4096                       @ 設定棧指標,以下都是C函式,呼叫前需要設好棧  
    bl  disable_watch_dog               @ 關閉WATCHDOG,否則CPU會不斷重啟  
    bl  memsetup                        @ 設定儲存控制器以使用SDRAM  
    bl  copy_2th_to_sdram               @ 將第二部分程式碼複製到SDRAM  
    bl  create_page_table               @ 設定頁表  
    bl  mmu_init                        @ 啟動MMU,啟動以後下面程式碼都用虛擬地址  
    ldr sp, =0xB4000000                 @ 重設棧指標,指向SDRAM頂端(使用虛擬地址)  
    ldr pc, =0xB0004000                 @ 跳到SDRAM中繼續執行第二部分程式碼  
halt_loop:  
    b   halt_loop  
   
   
/* 
 * init.c: 進行一些初始化,在Steppingstone中執行 
 * 它和head.S同屬第一部分程式,此時MMU未開啟,使用實體地址 
 */  
/* WATCHDOG暫存器 */  
#define WTCON           (*(volatile unsigned long *)0x53000000)  
/* 儲存控制器的暫存器起始地址 */  
#define MEM_CTL_BASE    0x48000000  
   
   
   
/* 
 * 關閉WATCHDOG,否則CPU會不斷重啟 
 */  
void disable_watch_dog(void)  
{  
    WTCON = 0;  // 關閉WATCHDOG很簡單,往這個暫存器寫0即可  
}  
   
  
/* 
 * 設定儲存控制器以使用SDRAM 
 */  
void memsetup(void)  
{  
    /* SDRAM 13個暫存器的值 */  
    unsigned long  const    mem_cfg_val[]={ 0x22011110,     //BWSCON  
                                            0x00000700,     //BANKCON0  
                                            0x00000700,     //BANKCON1  
                                            0x00000700,     //BANKCON2  
                                            0x00000700,     //BANKCON3   
                                            0x00000700,     //BANKCON4  
                                            0x00000700,     //BANKCON5  
                                            0x00018005,     //BANKCON6  
                                            0x00018005,     //BANKCON7  
                                            0x008C07A3,     //REFRESH  
                                            0x000000B1,     //BANKSIZE  
                                            0x00000030,     //MRSRB6  
                                            0x00000030,     //MRSRB7  
                                    };  
    int     i = 0;  
    volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;  
    for(; i < 13; i++)  
        p[i] = mem_cfg_val[i];        //迴圈複製13個暫存器到記憶體控制器基址  
}  
   
  
/* 
 * 將第二部分程式碼複製到SDRAM 
 */  
void copy_2th_to_sdram(void)  
{  
    unsigned int *pdwSrc  = (unsigned int *)2048;        //第二段程式碼載入地址2048  
    unsigned int *pdwDest = (unsigned int *)0x30004000;        //0x30004000前放頁表  
     
    while (pdwSrc < (unsigned int *)4096) //4kb最大4096  
    {  
        *pdwDest = *pdwSrc;  
        pdwDest++;  
        pdwSrc++;  
    }  
}  
   
  
/* 
 * 設定頁表 
 */  
void create_page_table(void)  
{  
   
  
/* 
 * 用於段描述符的一些巨集定義 
 *[31:20]段基址,[11:10]AP,[8:5]Domain,[3]C,[2]B,[1:0]0b10為段描述符 
 */  
#define MMU_FULL_ACCESS     (3 << 10)   /* 訪問許可權AP */  
#define MMU_DOMAIN          (0 << 5)    /* 屬於哪個域 Domain*/  
#define MMU_SPECIAL         (1 << 4)    /* 必須是1 */  
#define MMU_CACHEABLE       (1 << 3)    /* cacheable C位*/  
#define MMU_BUFFERABLE      (1 << 2)    /* bufferable B位*/  
#define MMU_SECTION         (2)         /* 表示這是段描述符 */  
#define MMU_SECDESC         (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | \  
                             MMU_SECTION)  
#define MMU_SECDESC_WB      (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | \  
                             MMU_CACHEABLE | MMU_BUFFERABLE | MMU_SECTION)  
#define MMU_SECTION_SIZE    0x00100000        /*每個段描述符對應1MB大小空間*/  
   
  
    unsigned long virtuladdr, physicaladdr;  
    unsigned long *mmu_tlb_base = (unsigned long *)0x30000000;        /*SDRAM開始地址存放頁表*/  
     
    /* 
     * Steppingstone的起始實體地址為0,第一部分程式的起始執行地址也是0, 
     * 為了在開啟MMU後仍能執行第一部分的程式, 
     * 將0~1M的虛擬地址對映到同樣的實體地址 
     */  
    virtuladdr = 0;  
    physicaladdr = 0;  
     //虛擬地址[31:20]用於索引一級頁表,找到它對應的描述符,對應於(virtualaddr>>20)  
     //段描述符中[31:20]儲存段的實體地址,對應(physicaladdr & 0xFFF00000)  
    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \  
                                            MMU_SECDESC_WB;  
   
  
    /* 
     * 0x56000000是GPIO暫存器的起始實體地址, 
     * GPBCON和GPBDAT這兩個暫存器的實體地址0x56000010、0x56000014, 
     * 為了在第二部分程式中能以地址0xA0000010、0xA0000014來操作GPBCON、GPBDAT, 
     * 把從0xA0000000開始的1M虛擬地址空間對映到從0x56000000開始的1M實體地址空間 
     */  
    virtuladdr = 0xA0000000;  
    physicaladdr = 0x56000000;  
    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \  
                                            MMU_SECDESC;  
   
  
    /* 
     * SDRAM的實體地址範圍是0x30000000~0x33FFFFFF, 
     * 將虛擬地址0xB0000000~0xB3FFFFFF對映到實體地址0x30000000~0x33FFFFFF上, 
     * 總共64M,涉及64個段描述符 
     */  
    virtuladdr = 0xB0000000;  
    physicaladdr = 0x30000000;  
    while (virtuladdr < 0xB4000000)  
    {  
        *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \  
                                                MMU_SECDESC_WB;  
        virtuladdr += 0x100000;        //右移20位就是1  
        physicaladdr += 0x100000;        //右移20位就是1  
    }  
}  
   
  
/* 
 * 啟動MMU 
 */  
void mmu_init(void)  
{  
    unsigned long ttb = 0x30000000;  
   
  
__asm__(  
    "mov    r0, #0\n"  
    "mcr    p15, 0, r0, c7, c7, 0\n"    /* 使無效ICaches和DCaches */  
     
    "mcr    p15, 0, r0, c7, c10, 4\n"   /* drain write buffer on v4 */  
    "mcr    p15, 0, r0, c8, c7, 0\n"    /* 使無效指令、資料TLB */  
     
    "mov    r4, %0\n"                   /* r4 = 頁表基址 */  
    "mcr    p15, 0, r4, c2, c0, 0\n"    /* 設定頁表基址暫存器 */  
     
    "mvn    r0, #0\n"                    
    "mcr    p15, 0, r0, c3, c0, 0\n"    /* 域訪問控制暫存器設為0xFFFFFFFF, 不進行許可權檢查*/     
    /* 
     * 對於控制暫存器,先讀出其值,在這基礎上修改感興趣的位, 
     * 然後再寫入 
     */  
    "mrc    p15, 0, r0, c1, c0, 0\n"    /* 讀出控制暫存器的值 */  
     
    /* 控制暫存器的低16位含義為:.RVI ..RS B... .CAM 
     * R : 表示換出Cache中的條目時使用的演算法, 
     *     0 = Random replacement;1 = Round robin replacement 
     * V : 表示異常向量表所在的位置, 
     *     0 = Low addresses = 0x00000000;1 = High addresses = 0xFFFF0000 
     * I : 0 = 關閉ICaches;1 = 開啟ICaches 
     * R、S : 用來與頁表中的描述符一起確定記憶體的訪問許可權 
     * B : 0 = CPU為小位元組序;1 = CPU為大位元組序 
     * C : 0 = 關閉DCaches;1 = 開啟DCaches 
     * A : 0 = 資料訪問時不進行地址對齊檢查;1 = 資料訪問時進行地址對齊檢查 
     * M : 0 = 關閉MMU;1 = 開啟MMU 
     */  
     
    /*  
     * 先清除不需要的位,往下若需要則重新設定它們    
     */  
                                        /* .RVI ..RS B... .CAM */  
    "bic    r0, r0, #0x3000\n"          /* ..11 .... .... .... 清除V、I位 */  
    "bic    r0, r0, #0x0300\n"          /* .... ..11 .... .... 清除R、S位 */  
    "bic    r0, r0, #0x0087\n"          /* .... .... 1... .111 清除B/C/A/M */  
   
  
    /* 
     * 設定需要的位 
     */  
    "orr    r0, r0, #0x0002\n"          /* .... .... .... ..1. 開啟對齊檢查 */  
    "orr    r0, r0, #0x0004\n"          /* .... .... .... .1.. 開啟DCaches */  
    "orr    r0, r0, #0x1000\n"          /* ...1 .... .... .... 開啟ICaches */  
    "orr    r0, r0, #0x0001\n"          /* .... .... .... ...1 使能MMU */  
     
    "mcr    p15, 0, r0, c1, c0, 0\n"    /* 將修改的值寫入控制暫存器 */  
    : /* 無輸出 */  
    : "r" (ttb) );  
}  
   
   
/* 
 * leds.c: 迴圈點亮4個LED 
 * 屬於第二部分程式,此時MMU已開啟,使用虛擬地址 
 */   
   
#define GPBCON      (*(volatile unsigned long *)0xA0000010)     // 實體地址0x56000010  
#define GPBDAT      (*(volatile unsigned long *)0xA0000014)     // 實體地址0x56000014  
   
#define GPB5_out    (1<<(5*2))  
#define GPB6_out    (1<<(6*2))  
#define GPB7_out    (1<<(7*2))  
#define GPB8_out    (1<<(8*2))  
   
/* 
 * wait函式加上“static inline”是有原因的, 
 * 這樣可以使得編譯leds.c時,wait嵌入main中,編譯結果中只有main一個函式。 
 * 於是在連線時,main函式的地址就是由連線檔案指定的執行時裝載地址。 
 * 而連線檔案mmu.lds中,指定了leds.o的執行時裝載地址為0xB4004000, 
 * 這樣,head.S中的“ldr pc, =0xB4004000”就是跳去執行main函式。 
 */  
static inline void wait(unsigned long dly)  
{  
    for(; dly > 0; dly--);  
}  
   
int main(void)  
{  
    unsigned long i = 0;  
      
    // 將LED1-4對應的GPB5/6/7/8四個引腳設為輸出  
    GPBCON = GPB5_out|GPB6_out|GPB7_out|GPB8_out;         
   
    while(1){  
        wait(30000);  
        GPBDAT = (~(i<<5));     // 根據i的值,點亮LED1-4  
        if(++i == 16)  
            i = 0;  
    }  
   
    return 0;  
}  
   
最後是Makefile  
  
objs := head.o init.o leds.o  
  
mmu.bin : $(objs)  
arm-linux-ld -Tmmu.lds -o mmu_elf $^  
arm-linux-objcopy -O binary -S mmu_elf [email protected]  
arm-linux-objdump -D -m arm mmu_elf > mmu.dis  
%.o:%.c  
arm-linux-gcc -Wall -O2 -c -o [email protected] $<  
  
%.o:%.S  
arm-linux-gcc -Wall -O2 -c -o [email protected] $<  
  
clean:  
rm -f mmu.bin mmu_elf mmu.dis *.o  





















讀/寫