一、內存管理單元MMU介紹
內存管理單元簡稱MMU,它負責虛擬地址到物理地址的映射,並提供硬件機制的內存訪問權限檢查。MMU使得每個用戶進程擁有自己獨立的地址空間,並通過內存訪問權限的檢查保護每個進程所用的內存不被其他進程破壞。
重點就在於地址映射:頁表的結構與建立、映射的過程。
1、S3C2440 MMU地址變換過程
1)地址的分類
虛擬地址最終需要轉換為物理地址才能讀寫實際的數據,通過將虛擬地址空間和物理空間划分為同樣大小的空間(段或頁),然后兩個空間建立映射關系。
由於虛擬地址空間遠大於物理地址,可能多塊虛擬地址空間映射到同一塊物理地址空間,或者有些虛擬地址空間沒有映射到具體的物理地址空間上去(使用到時再映射)。
ARM cpu地址轉換涉及三種地址:虛擬地址(VA,Virtual Address)、變換后的虛擬地址(MVA,Modified Virtual Address)、物理地址(PA,Physical Address)
沒有啟動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
MVA是除CPU核心外的其他部分看到的虛擬地址,VA與MVA的變化關系
如果VA<32M,需要使用進程標識號PID(通過讀CP15的C13獲得)來轉換為MVA
if (VA < 32M) then
MVA = VA | (PID << 25)
else
MVA = VA
使用MVA,而不使用VA的目的是,當有重疊的VA時,轉換為MVA地址並不重疊,減小轉換為PA的代價
比如兩個進程1、2,VA都是0-(32M-1),則MVA分別為0x02000000-0x03ffffff,0x04000000-0x05ffffff。
下文說到虛擬地址,如果沒有特別指出,就是指MVA
2)虛擬地址到物理地址的轉換過程
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:無效
0b01:粗頁表(Coarse page table)
[31:10]為粗頁表基址,此描述符低10位填充0后就是一個二級頁表的物理地址,二級頁表含256個條目(使用[9:2],2^8=256個),稱為粗頁表(Coarse page table)。其中每個條目表示4KB大小的物理地址空間,一個粗頁表表示1MB物理地址
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)
段地址轉換過程
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 $@
arm-linux-objdump -D -m arm mmu_elf > mmu.dis
%.o:%.c
arm-linux-gcc -Wall -O2 -c -o $@ $<
%.o:%.S
arm-linux-gcc -Wall -O2 -c -o $@ $<
clean:
rm -f mmu.bin mmu_elf mmu.dis *.o
本文來源於:DoDo's Blog http://www.sectop.com/ , 原文地址:http://www.sectop.com/post/83.html
一、內存管理單元MMU介紹
內存管理單元簡稱MMU,它負責虛擬地址到物理地址的映射,並提供硬件機制的內存訪問權限檢查。MMU使得每個用戶進程擁有自己獨立的地址空間,並通過內存訪問權限的檢查保護每個進程所用的內存不被其他進程破壞。
重點就在於地址映射:頁表的結構與建立、映射的過程。
1、S3C2440 MMU地址變換過程
1)地址的分類
一個程序在運行之前,沒有必要全部裝入內存,僅需要將那些要運行的部分先裝入內存,其余部分在用到時從磁盤載入,當內存不足時,再將暫時不用的部分調出到磁盤。
這使得大程序可以在較小的內存空間中運行,也使得內存中可以同時裝入更多的程序並發執行,這樣的存儲器一般稱為虛擬存儲器。
虛擬地址最終需要轉換為物理地址才能讀寫實際的數據,通過將虛擬地址空間和物理空間划分為同樣大小的空間(段或頁),然后兩個空間建立映射關系。
由於虛擬地址空間遠大於物理地址,可能多塊虛擬地址空間映射到同一塊物理地址空間,或者有些虛擬地址空間沒有映射到具體的物理地址空間上去(使用到時再映射)。
ARM cpu地址轉換涉及三種地址:虛擬地址(VA,Virtual Address)、變換后的虛擬地址(MVA,Modified Virtual Address)、物理地址(PA,Physical Address)
沒有啟動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
MVA是除CPU核心外的其他部分看到的虛擬地址,VA與MVA的變化關系
如果VA<32M,需要使用進程標識號PID(通過讀CP15的C13獲得)來轉換為MVA
if (VA < 32M) then
MVA = VA | (PID << 25)
else
MVA = VA
使用MVA,而不使用VA的目的是,當有重疊的VA時,轉換為MVA地址並不重疊,減小轉換為PA的代價
比如兩個進程1、2,VA都是0-(32M-1),則MVA分別為0x02000000-0x03ffffff,0x04000000-0x05ffffff。
下文說到虛擬地址,如果沒有特別指出,就是指MVA
2)虛擬地址到物理地址的轉換過程
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:無效
0b01:粗頁表(Coarse page table)
[31:10]為粗頁表基址,此描述符低10位填充0后就是一個二級頁表的物理地址,二級頁表含256個條目(使用[9:2],2^8=256個),稱為粗頁表(Coarse page table)。其中每個條目表示4KB大小的物理地址空間,一個粗頁表表示1MB物理地址
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)
段地址轉換過程
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 $@
arm-linux-objdump -D -m arm mmu_elf > mmu.dis
%.o:%.c
arm-linux-gcc -Wall -O2 -c -o $@ $<
%.o:%.S
arm-linux-gcc -Wall -O2 -c -o $@ $<
clean:
rm -f mmu.bin mmu_elf mmu.dis *.o