ARMv7架構支持安全擴展,如果使能了安全擴展,ARMv7-A架構分為安全模式(Secure State)和非安全模式(Non-secure State)兩個世界。
在非安全模式下,存在三種運行特權 PL0,PL1和 PL2(Privilege level)。(這里僅僅討論非安全 State)
特權等級 | 描述 |
---|---|
PL0 | PL0運行在用戶模式(User),用于運行應用程序。該模式程序受限訪問系統(tǒng)資源。對應Linux用戶態(tài)。 |
PL1 | PL1運行非用戶模式和Hyp模式外的所有模式。Linux內核運行在PL1。包含了ARMv6架構中的System,SVC,FIQ,IRQ,UNDEF及Abort模式。此外,安全模式中的Montior也運行在PL1等級,管理安全模式和非安全模式的切換。 |
PL2 | PL2用于虛擬化。虛擬化超級管理程序(Hypervisor)運行在 PL2。 |
這里的 PL0~PL2 指的是特權等級,不同的特權等級,訪問資源的權限不一樣,操作系統(tǒng)運行再 PL1 的特權等級下,具有較高的訪問權限,用戶態(tài)運行再 PL0 的特權等級下(也叫非特權等級),只有最最基本的訪問權限;
上面說的是處理器的特權等級,那么處理器真正的運行的模式有幾種呢?如下所示:
可以看到,ARMv7-A 的處理器模式有 User、FIQ、IRQ、Supervisor、Monitor、Abort、Hyp、Undefined、System 模式:
User:用戶模式,運行再 PL0 這個特權等級上,也就是沒有特權等級,他是OS上運行應用程序時候的等級,他不可以訪問系統(tǒng)資源(諸如 MMU 等),在這個模式下,無法主動切換模式,除非遇到中斷或者異常(諸如 SWI 觸發(fā)系統(tǒng)調用);
FIQ:快中斷模式,發(fā)生 FIQ 快中斷的時候處理器模式;
IRQ:中斷模式,發(fā)生 IRQ 快中斷的時候處理器模式;
Supervisor:管理員模式,復位后的默認模式,運行再 PL1 特權等級,可以通過 SWI(SVC) 系統(tǒng)調用呼叫產(chǎn)生 Supervisor Call 異常,進入 Supervisor 模式,操作系統(tǒng)常用的模式;
Monitor:監(jiān)視模式,針對 Security 擴展,不詳細討論;
Abort:停止模式,當發(fā)生 Data Abort exception 或者 Prefetch Abort exception 異常時候進入這個模式;
Hyp:當支持虛擬化擴展的時候模式,不詳細討論;
Undefined:這是執(zhí)行和指令相關的模式,當企圖執(zhí)行 UNDEFINED 指令的時候進入這個模式;
System:系統(tǒng)模式,也是 PL1 特權等級,和 Supervisor 的區(qū)別是,System 模式具有和 User 模式一樣的寄存器,目前大多數(shù)系統(tǒng)未使用;
ARMv7-A 處理器有 16 個通用寄存器:R0~R15,其中:
R13:通常用做堆棧指針 SP;
R14:通常用作鏈接寄存器 LR;
R15:通常用作程序計數(shù)器 PC;
前面說了處理器有特權等級,每種特權等級訪問系統(tǒng)資源的權限不一樣,而處理器又有幾種模式,每種模式對應的特權等級有一定區(qū)別;
每一種處理器模式對應的寄存器也有一定區(qū)別:
從上圖可以看出:
1、R0~R7,PC是所有模式下共享的;
2、FIQ 模式下,R8~R12、SP、LR 都是有專門的寄存器,有的材料上,稱之為“影子寄存器”,什么意思呢?這個模式下,有他專用的 R8~R12、SP、LR;
3、同樣道理,Supervisor、Abort、Undefined、IRQ 等,都有他們自己模式下專用的 SP 和 LR,也就是說,從其他模式進來的時候,不需要針對這兩個寄存器進行恢復現(xiàn)場;
4、FIQ 之所以稱之為 FIQ,從軟件上也看得出來,他專用的寄存器要多于 IRQ 的,所以也的確是要 Fast 一些;
從這里,我們也可以看出,exception 發(fā)生的時候,我們其實是有必要手動保存一些現(xiàn)場的;
ARMv7-A 還有一個特殊寄存器叫:程序狀態(tài)寄存器 CPSR(Current Program Status Register),再進入異常之前,當前的 CPSR 被保存到 SPSR (Saved Program Status Register)中;
當然 CPRS 再用戶層叫做 APSR,APSR 只是 CPSR 寄存器中被截取的一部分,因為在用戶層,并不是所有的 CPSR 的位都可以訪問;
CPSR 的組成如下所示:
Field | 作用 |
---|---|
N | ALU返回運算結果是否為負數(shù) |
Z | ALU返回運算結果是否為0 |
C | ALU運算是否發(fā)生進位 |
V | ALU運算是否發(fā)生溢出 |
Q | cumulative saturation |
J | ARM是否處于 Jazelle 狀態(tài) |
E | 控制 load/store 字節(jié)序,E=1表示大端模式,E=0表示小端模式; |
A | disables asynchronous aborts,User模式不能操作 |
I | 使能/禁能 IRQ,User模式不能操作,I=1表示禁止 IRQ,I=0表示使能 IRQ; |
F | 使能/禁能 FIQ,User模式不能操作,F(xiàn)=1表示禁止 FIQ,I=0表示使能 FIQ; |
T | ARM和Thumb狀態(tài)標志位 |
GE | 用于某些SIMD(Single Instruction, Multiple Data)指令 |
M[4:0] | 處理器模式:FIQ,IRQ,ABT,SVC,UND,MON,HYP。User模式不能操作 |
IT[7:0] | IT[7:2](bit15:10):和IT[1:0](bit26:25)一起組成IT[7:0],表示IF-THEN指令的執(zhí)行狀態(tài); |
這里的 M[4:0] 就是直接對應到了前面講到的模式,還記得在前面那個處理器模式的列表中,每一個模式都對應了一個 Encoding 嗎?這個 Encoding 就是這個 M 位的值;
ARMv7-A 支持 32bit ARM 指令集的同時,還支持 16bit 的 Thumb 指令集,它具有更好的代碼密度,處理器可以在這兩種指令集之間切換;
所有的Cortex-A系列處理器實現(xiàn)了Thumb-2技術,它擴展了Thumb指令集。混合使用32位和16位指令,以Thumb指令集的代碼密度和接近ARM指令集的性能。自從所有的Cortex-A系列處理器支持這一擴展,針對它們的軟件常被編譯成Thumb指令集;
ARM 處理是加載/存儲體系結構的典型的RISC處理器,對存儲器的訪問只能使用加載和存儲指令實現(xiàn)。ARM 的加載/存儲指令是可以實現(xiàn)字、半字、無符/有符字節(jié)操作;批量加載/存儲指令可實現(xiàn)一條指令加載/存儲多個寄存器的內容,大大提高效率;
基本格式為:
<opcode>{<cond>}{S} <Rd>,<Rn>{,<opcode2>}
<>內的項是必須的,{}內的項是可選的,如<opcode>是指令助記符,是必須的,而{<cond>}為指令執(zhí)行條件,是可選的,如果不寫則使用默認條件AL(無條件執(zhí)行)
opcode:指令助記符,如 LDR,STR 等
cond:執(zhí)行條件,如EQ,NE 等
S:是否影響CPSR 寄存器的值,書寫時影響CPSR,否則不影響
Rd :目標寄存器
Rn:第一個操作數(shù)的寄存器
operand2:第二個操作數(shù)
LDR指令用于從內存中讀取數(shù)據(jù)放入寄存器中;STR 指令用于將寄存器中的數(shù)據(jù)保存到內存。指令格式如下:
LDR{cond}{T} Rd,<地址>; 加載指定地址上的數(shù)據(jù)(字),放入Rd中
STR{cond}{T} Rd,<地址>; 存儲數(shù)據(jù)(字)到指定地址的存儲單元,要存儲的數(shù)據(jù)在Rd中
LDR/STR 指令尋址是非常靈活的,由兩部分組成,一部分為一個基址寄存器,可以為任一個通用寄存器,另一部分為一個地址偏移量。地址偏移量有以下3種格式:
(1) 立即數(shù)。立即數(shù)可以是一個無符號數(shù)值,這個數(shù)據(jù)可以加到基址寄存器,也可以從基址寄存器中減去這個數(shù)值。指令舉例如下:
- LDR R0,=0X123 ; 將0X123存入r0中
- LDR R0,=label ; 將label_1所指向的地址值存入r0中
- LDR R1,[R0] ; 將 R0 地址處的數(shù)據(jù)讀出,保存到R1中 (R0 的值不變)
- LDR R1,[R0,#0x12] ; 將 R0+0x12 地址處的數(shù)據(jù)讀出,保存到R1中 (R0 的值不變)
- LDR R1,[R0,#-0x12]; 將 R0-0x12 地址處的數(shù)據(jù)讀出,保存到R1中 (R0 的值不變)
(2)寄存器。寄存器中的數(shù)值可以加到基址寄存器,也可以從基址寄存器中減去這個數(shù)值。指令舉例值。指令舉例如下:
- LDR R1,[R0,R2] ; 將R0+R2 地址的數(shù)據(jù)計讀出,保存到R1中(R0 的值不變)
- LDR R1,[R0,-R2] ; 將R0-R2 地址的數(shù)據(jù)計讀出,保存到R1中(R0 的值不變)
(3)寄存器及移位常數(shù)。寄存器移位后的值可以加到基址寄存器,也可以從基址寄存器中減去這個數(shù)值。指令舉例如下:
- LDR R1,[R0,R2,LSL #2] ;將R0+R2*4地址處的數(shù)據(jù)讀出,保存到R1中(R0,R2的值不變)
- LDR R1,[R0,-R2,LSL #2];將R0-R2*4地址處的數(shù)據(jù)計讀出,保存到R1中(R0,R2的值不變)
一組代碼示例:
NumCount EQU 0x40003000 ;定義變量NumCount … LDR R0,=NumCount ;使用LDR 偽指令裝載NumCount的地址到R0 LDR R1,[R0] ;取出變量值 ADD R1,R1,#1 ;NumCount=NumCount+1 STR R1,[R0] ;保存變量值 … GPIO 設置 GPIO-BASE EQU 0Xe0028000 ;定義GPIO 寄存器的基地址 … LDR R0,=GPIO-BASE LDR R1,=0x00FFFF00 ;裝載32 位立即數(shù),即設置值 STR R1,[R0,#0x0C] ;IODIR=0x00FFFF00, IODIR 的地址為0xE002800C MOV R1,#0x00F00000 STR R1,[R0,#0x04] ;IOSET=0x00F00000,IOSET 的地址為0xE0028004
批量加載/存儲指令可以實現(xiàn)在一組寄存器和一塊連續(xù)的內存單元之間傳輸數(shù)據(jù)。LDM 為加載多個寄存器,STM 為存儲多個寄存器。允許一條指令傳送16 個寄存器的任何子集或所有寄存器。指令格式如下:
LDM{cond}<模式> Rn{!},reglist{^}
STM{cond}<模式> Rn{!},reglist{^}
LDM /STM 的主要用途是現(xiàn)場保護、數(shù)據(jù)復制、參數(shù)傳送等。其模式有8種,如下所列:(前面4 種用于數(shù)據(jù)塊的傳輸,后面4 種是堆棧操作)。
(1) IA:每次傳送后地址加4
(2) IB:每次傳送前地址加4
(3) DA:每次傳送后地址減4
(4) DB:每次傳送前地址減4
(5) FD:滿遞減堆棧
(6) ED:空遞增堆棧
(7) FA:滿遞增堆棧
(8) EA:空遞增堆棧
其中,寄存器Rn 為基址寄存器,裝有傳送數(shù)據(jù)的初始地址,Rn 不允許為R15;后綴“!”表示最后的地址寫回到Rn中;寄存器列表reglist 可包含多于一個寄存器或寄存器范圍,使用“,”分開,如{R1,R2,R6-R9},寄存器排列由小到大排列;“^”后綴不允許在用戶模式呈系統(tǒng)模式下使用,若在LDM 指令用寄存器列表中包含有PC 時使用,那么除了正常的多寄存器傳送外,將SPSR 拷貝到CPSR 中,這可用于異常處理返回;使用“^”后綴進行數(shù)據(jù)傳送且寄存器列表不包含PC時,加載/存儲的是用戶模式的寄存器,而不是當前模式的寄存器。
地址對準――這些指令忽略地址的位[1:0]。
批量加載/存儲指令舉例如下:
LDMIA R0!,{R3-R9} ;加載R0 指向的地址上的多字數(shù)據(jù),保存到R3~R9中,R0 值更新
STMIA R1!,{R3-R9} ;將R3~R9 的數(shù)據(jù)存儲到R1 指向的地址上,R1值更新
STMFD SP!,{R0-R7,LR} ;現(xiàn)場保存,將R0~R7、LR入棧
LDMFD SP!,{R0-R7,PC}^;恢復現(xiàn)場,異常處理返回
在進行數(shù)據(jù)復制時,先設置好源數(shù)據(jù)指針,然后使用塊拷貝尋址指令LDMIA/STMIA、LDMIB/STMIB、LDMDA/STMDA、LDMDB/STMDB 進行讀取和存儲。而進行堆棧操作時,則要先設置堆棧指針,一般使用SP 然后使用堆棧尋址指令STMFD/LDMFD、STMED。LDMED、STMFA/LDMFA、STMEA/LDMEA實現(xiàn)堆棧操作。
使用LDM/STM 進行數(shù)據(jù)復制例程如下:
- …
- LDR R0,=SrcData ;設置源數(shù)據(jù)地址
- LDR R1,=DstData ;設置目標地址
- LDMIA R0,{R2-R9} ;加載8 字數(shù)據(jù)到寄存器R2~R9
- STMIA R1,{R2-R9} ;存儲寄存器R2~R9 到目標地址
- 使用LDM/STM 進行現(xiàn)場寄存器保護,常在子程序中或異常處理使用:
- SENDBYTE
- STMFD SP!,{R0-R7,LR} ;寄存器入堆
- …
- BL DELAY ;調用DELAY 子程序
- …
- LDMFD SP!,{R0-R7,PC} ;恢復寄存器,并返回
值得注意的是一些諸如原子操作的指令:STREX/LDREX;
特殊寄存器 CPSR 通過 MRS 和 MSR 指令進行讀寫操作:
MRS:讀狀態(tài)寄存器指令。在ARM 處理器中,只有 MRS 指令可以狀態(tài)寄存器CPSR或SPSR讀出到通用寄存器中。
MRS{cond} Rd ,psr
Rd 目標寄存器。Rd 不允許為R15
舉例:
- MRS R1,CPSR ;將CPSR狀態(tài)寄存器讀取,保存到R1 中
- MRS R2,SPSR ;將SPSR狀態(tài)寄存器讀取,保存到R2 中
MRS 指令讀取CPSR,可用來判斷ALU 的狀態(tài)標志,或IRQ、FIQ中斷是否允許等;在異常處理程序中,讀SPSR 可知道進行異常前的處理器狀態(tài)等。MRS 與MSR 配合使用,實現(xiàn)CPSR 或SPSR 寄存器的讀—修改---寫操作,可用來進行處理器模式切換(),允許/禁止IRQ/FIQ中斷等設置。另外,進程切換或允許異常中斷嵌套時,也需要使用MRS 指令讀取SPSR 狀態(tài)值。保存起來
舉例:
- 使能IRQ 中斷例程:
- ENABLE_IRQ
- MRS R0,CPSR
- BIC R0,R0,#0x80
- MSR CPSR_c,R0
- MOV PC,LR
- 禁能IRQ 中斷例程:
- DISABLE_IRQ
- MRS R0,CPSR
- ORR R0,R0,#0x80
- MSR CPSR_c,R0
- MOV PC,LR
MSR:寫狀態(tài)寄存器指令。在ARM 處理器中。只有MSR 指令可以直接設置狀態(tài)寄存器CPSR或SPSR。指令格式如下:
MSR{cond} psr_fields,#immed_8r
MSR{cond} psr_fields,Rm
psr CPSR 或 SPSR,
fields 指定傳送的區(qū)域。Fields 可以是以下的一種或多種(字母必須為小寫):
c 控制域屏蔽字節(jié)(psr[7…0])
x 擴展域屏蔽字節(jié)(psr[15…8])
s 狀態(tài)域屏蔽字節(jié)(psr[23。…16])
f 標志域屏蔽字節(jié)(psr[31…24])
immed_8r 要傳送到狀態(tài)寄存器指定域的立即數(shù),8 位。
Rm 要傳送到狀態(tài)寄存器指定域的數(shù)據(jù)的源寄存器。
MSR 指令舉例如下:
- MSR CPSR_c,#0xD3 ;CPSR[7…0]=0xD3,即切換到管理模式。
- MSR CPSR_cxsf,R3 ;CPSR=R3
注意:只有在特權模式下才能修改狀態(tài)寄存器!
程序中不能通過MSR 指令直接修改CPSR 中的 T 控制位來實現(xiàn)ARM 狀態(tài)/Thumb狀態(tài)的切換,必須使用 BX 指令完成處理器狀態(tài)的切換(因為BX 指令屬轉移指令,它會打斷流水線狀態(tài),實現(xiàn)處理器狀態(tài)切換)。MRS 與MSR 配合使用,實現(xiàn)CPSR或SPSR 寄存器的讀-修改-寫操作,可用來進行處理器模式切換、允許/禁止IRQ/FIQ 中斷等設置。
- 堆棧指令實始化例程:
- INITSTACK
- MOV R0,LR ;保存返回地址
- ;設置管理模式堆棧
- MSR CPSR_c,#0xD3
- LDR SP,StackSvc
- ;設置中斷模式堆棧
- MSR CPSR_c,#0xD2
- LDR SP,StackIrq
更多指令集 相關的詳細內容,參考 ARM 官方文檔,或者:
更多關于協(xié)處理器 Cp15 以及 Cache 和 MMU 相關的,在下一章介紹;
ARMv7-A 處理器除了標準的 R0~R15,CPSR,SPSR 以外,由于引入了 MMU、TLB、Cache 等內容,ARMv7-A 使用協(xié)處理器來對這些擴展來進行管理,ARMv7-A 支持 16 個協(xié)處理器,編號從 CP0~CP15,其中的 CP15 協(xié)處理器稱之為系統(tǒng)控制協(xié)處理器,CP15 協(xié)處理器下的寄存器包含了 MMU、TLB、Cache等關鍵組件,其余的 CP0~CP14 有的控制Debug功能,有的控制 SIMD,有的控制浮點,咱們暫時只關注關鍵的 CP15;
CP15 協(xié)處理器由16個子寄存器組成,分別為 c0~c15,所以 CP15 的層次關系為:
這里的 c0~c15 不是寄存器的含義,而是主(Primary Register)寄存器的意思,也就是每個 c0~c15 中,包含很多寄存器組:
比較常用的 c0~c15 的寄存器組織如下
register | physical register | 描述 |
---|---|---|
c0 | MIDR | 主ID寄存器,用于記錄廠商版本信息 |
MPIDR | 多核處理器情況下,配置 Affinity | |
c1 | SCTLR | 系統(tǒng)控制寄存器 |
ACTLR | 輔助控制寄存器 | |
CPACR | 協(xié)處理器訪問控制寄存器,控制訪問除了CP14和CP15的協(xié)處理器 | |
SCR | 安全配置寄存器 | |
c2 c3 | TTBR0 | 一級轉換頁表基址寄存器0 |
TTBR1 | 一級轉換頁表基址寄存器1 | |
TTBCR | 頁表轉換控制寄存器 | |
c5 c6 | DFSR | 數(shù)據(jù)異常(Data Fault)狀態(tài)寄存器 |
IFSR | 指令異常(Instruction Fault)狀態(tài)寄存器 | |
DFAR | 數(shù)據(jù)異常(Data Fault)地址寄存器 | |
IFAR | 指令異常(Instruction Fault)地址寄存器 | |
c7 | predictor | cache及分支預測 |
barrier | 數(shù)據(jù)及指令屏障 | |
c8 | TLB | TLB操作 |
c9 | performance monitors | 性能監(jiān)視器 |
c12 | VBAR | 非安全模式異常基址寄存器 |
MVBAR | 安全模式異?;芳拇嫫?/td> | |
c13 | ASID | 上下文ID寄存器,軟件線程ID寄存器 |
c15 | CBAR | 配置基址寄存器,用于GIC(Generic Interrupt Controller)和定時器類型外設 |
所以,針對 CP15 協(xié)處理器相關的層次結構總結下來為(這里以c0為例,c1~c15同樣有很多寄存器,每個寄存器都是 32bits 的):
與 CPSR 類似,協(xié)處理器的訪問是通過指定的匯編指令進行訪問;常用的有 MCR/MRC 兩條:
MRC: 將 CP15 協(xié)處理器中的寄存器數(shù)據(jù)讀到 ARM 寄存器中。
MCR: 將 ARM 寄存器的數(shù)據(jù)寫入到 CP15 協(xié)處理器寄存器中。
使用這兩條指令,外加一些標準的指令,就可以實現(xiàn)讀改寫;
MCR 指令的格式如下:
MCR<c> <coproc>, <opc1>, <Rt>, <CRn>, <CRm>{, <opc2>}
其中:
coproc:訪問協(xié)處理器的名字,取值范圍從 p0~p15;
opc1:協(xié)處理器要執(zhí)行的操作碼,取值范圍從 0~7;
Rt:ARM 的寄存器(比如 R0),要寫入到指定協(xié)處理器寄存器的數(shù)據(jù)就保存在此寄存器中;
CRn:指定協(xié)處理器的目標寄存器;
CRm:協(xié)處理器中附加的目標寄存器或者源操作數(shù)寄存器,如果不需要附加信息就將 CRm 設置為 C0,否則結果不可預測;
opc2:可選的協(xié)處理器特定操作碼,取值范圍從 0~7,當不需要的時候要設置為 0;
MRC 指令的格式如下:
MRC<c> <coproc>, <opc1>, <Rt>, <CRn>, <CRm>{, <opc2>}
其中:
coproc:訪問協(xié)處理器的名字,取值范圍從 p0~p15;
opc1:協(xié)處理器要執(zhí)行的操作碼,取值范圍從 0~7;
Rt:ARM 的寄存器(比如 R0),將指定協(xié)處理器寄存器的數(shù)據(jù)讀在此 ARM Core 寄存器中;
CRn:指定協(xié)處理器的目標寄存器;
CRm:協(xié)處理器中附加的目標寄存器或者源操作數(shù)寄存器,如果不需要附加信息就將 CRm 設置為 C0,否則結果不可預測;
opc2:可選的協(xié)處理器特定操作碼,取值范圍從 0~7,當不需要的時候要設置為 0;
訪問協(xié)處理器寄存器的指令已經(jīng)寫得很清楚,接下來就是如何來填這指令中的變量,這涉及到具體的協(xié)處理器的寄存器組織,我們使用最常用的 CP15 為例進行講述;
不管是 MCR/MRC 指令,如果針對到具體的 Cp15 協(xié)處理器,coproc 字段填 p15,Rt 字段可以隨便指定,暫時不管,那么就剩下 4 個變量:
{opc1、CRn、CRm、opc2};
Cp15 協(xié)處理器的所有寄存器訪問,都要依賴這幾個值的組合達到訪問的目的,前面說過整個 Cp15 的寄存器,分為 c0~c15,一個 16 個 Primary Regiser,再在每個 Primary Regiser 下面,又細分了很多具體的每個長度為 32bits 的寄存器,他們的整體組織結構為:
可以看到,Cp15 協(xié)處理器的 CRn 編號從 c0~c15,每個 Primary Regiser 都標記得有訪問權限;后面的 opc1、CRm、opc2 的取值訪問也都標記得清清楚楚;
那么下面將 c0~c16 每個展開看一下便可以清清楚楚看到他們怎么組織起來的,這樣就可以知道如何使用匯編進行編碼配置;
c0 的寄存器組成如下:
訪問每個寄存器的 {opc1、CRn、CRm、opc2} 寫的清清楚楚;
主要是和 ID 相關的內容;
c1 的寄存器組成如下:
主要是和系統(tǒng)控制相關的配置;
c2&c3 的寄存器組成如下:
主要是內存保護和控制相關的(MMU);
Cp15 協(xié)處理器的 c4 寄存器為 Not used;
c5&c6 的寄存器組成如下:
主要是和 memory fault 相關的;
c7 的寄存器組成如下:
主要是和 Cache 相關的部分;
c8 的寄存器組成如下:
主要是和 TLB 相關的;
c9 的寄存器是為 cache 和 TCM 預留的;
c10 的寄存器是為內存重映射和 TLB 控制:
c11 的寄存器是為 TCM DMA 預留:
c12 的寄存器是為安全擴展的:
c13 的寄存器是為進程,上下文和線程 ID 的:
c14 的寄存器是為通用 Timer 預留的:
c15 的寄存器是 IMPLEMENTATION DEFINED 的,不管他:
參考文獻:
ARM體系架構—ARMv7-A協(xié)處理器_liyuewuwunaile的博客-CSDN博客
ARM體系架構—ARMv7-A指令集:協(xié)處理器指令_liyuewuwunaile的博客-CSDN博客
ARMv7 CP15協(xié)處理器詳解_Deep_l_zh的博客-CSDN博客_cp15協(xié)處理器
本文參考:
《ARMv7-A_and_R_Architecture_Reference_Manual》 中的 {A3.5 Memory types and attributes and the memory order model}
ARMv7-A 處理器中,將 Memory定義為幾種類型(Memory Type):
1、Strongly-ordered;
2、Normal;
3、Device;
它的定義如下所示:
注意:這里的 Memory 指的不是內存,可以翻譯成儲存器,是地址空間的概念;
普通的內存(RAM),只讀的內存(ROM),這些都屬于 Normal Type 的范疇;
外設和I/O,這些屬于 Device 和 Strongly-ordered 范疇;
對于地址空間來說,每種地址空間,ARMv7-A 使用 attribute 來描述這個存儲器地址的屬性,總的屬性分為兩種:
1、shareability;共享屬性
2、cacheability;緩存屬性
它們描述了存儲器是否可以共享,或者是否可以過cache;
Shareability 共享屬性用于描述存儲器地址空間是否可以共享;只對 Normal Type 類型和 Device 類型有效;
Normal Type 是比較常見的存儲器模型,它可以支持讀/寫或只讀,它可以被配置成為 Shareable 或者 Non-Shareable;
在訪問 Normal 存儲器的時候(比如 DDR),一定要小心內存一致性的問題,因為系統(tǒng)中,常常會開啟 cache,并在多核系統(tǒng)中,存在多 CPU 核心訪問內存,內存屏障能夠起到訪問的保序作用,原子操作指令,可以起到訪問互斥(LDREX/STREX);
對于 Normal Type 的內存屬性描述如下:
當被配置成為 Non-Shareable 的時候,意味著在多核系統(tǒng)中,它只能被一個核心訪問;
當配置為 Inner-Shareable 的時候,意味著只能夠被單個 CPU cluster 集群訪問;
當配置為 Outer-Shareable 的時候,意味著只能夠被多個 CPU clusters 集群訪問;
Device 類型存儲器地址空間,可以配置為 Shareable(比如多核 CPU 中,UART 外設地址空間是對所有 CPU 共享的);當然,也可以配置為 Non-Shareable;
但是具體實際的設計實現(xiàn)中,不管是配置為 Non-Shareable,還是 Inner-Shareable,還是 Outer-Shareable,都一概視為 Outer-Shareable(畢竟是外設);
Normal 類型的存儲器(比如 DDR)除了有 Shareable 的屬性以外,還可以攜帶 cacheable 的屬性:
Write-Through Cacheable:寫透型 Cache 屬性;
Write-Back Cacheable:回寫型 Cache 屬性;
Non-cacheable:不帶 Cache 屬性;
Non-Cacheable 很好理解,就是不帶 Cache,直接與存儲器交互,這樣不會帶來內存一致性問題,但是效率不高;
其余兩種都是帶 Cache 的訪問,不過 Cache 的策略略有區(qū)別;寫透也可以在一定程度上保證內存一致性問題,但要發(fā)起內存訪問時序,降低性能;回寫只是將數(shù)據(jù)寫到了 Cache 中,并通過 Dirty 標記等方式來記錄數(shù)據(jù)的有效性,從而避免直接的內存訪問,可以提高性能;
Device 和 Strongly-ordered 類型的存儲器(可以理解為外設)區(qū)域,都是 Non-cacheable 的,即,不緩存的;這個很好理解,寫過驅動的朋友應該都能夠知道,對外設的訪問和對普通內存的訪問是完全不一樣的兩個概念;
所以,最后總結起來就是:
ARMv7-A 中,Device 類型的存儲器和 Strongly-ordered 類型存儲器,都是不可 Cacheable 的,并且他們的訪問是不可被優(yōu)化的,他們的唯一區(qū)別是在 Shareability 屬性:
1、對 Strongly-ordered 存儲器的寫入,只有在寫訪問到外設或者存儲器組件的時候,才算完成;
2、對 Device 存儲器的寫入,允許在寫訪問到達外設之前就完成;
上面這個說法是官方的意思,我理解一下,應該是 Strongly-ordered 不惜犧牲性能,去做保序的要求,一定要實際訪問到外設,而 Device 類型的訪問指令,可能還在路上(流水線中);
而且,Strongly-ordered 和 Device 類型要求,對他們的訪問必須是對齊訪問;
存儲器模型除了有各種屬性以外,還需要支持訪問權限(這個是必須的,否則訪問非法空間,那還得了);
ARMv7-A 定義了擴展的存儲器區(qū)域訪問屬性,也就是這個權限訪問;訪問權限分為兩部分:
1、基于特權等級的限制數(shù)據(jù)的訪問;
2、基于特權等級的指令訪問;
PL0 的時候,也就是 User 模式,相當于非特權等級下,禁止訪問結構體系下的某些特性,它不能更改許多配置設置。 在PL0上執(zhí)行的軟件只允許非特權內存訪問;
當在 PL1 上執(zhí)行的軟件(通常操作系統(tǒng)都執(zhí)行在 PL1 特權模式下)可以訪問體系結構的所有特性,并且可以更改這些特性的配置設置;
值得注意的是,fa權限的配置以及更改,只能夠在 PL1 模式下進行;
針對數(shù)據(jù)的訪問來說,在 PL1 模式下,可以定義某存儲器區(qū)域的訪問權限為:
1、不可訪問;
2、只有 PL1 特權才能訪問;
3、特權和非特權模式都可以訪問;
如果處理器嘗試訪問權限不允許的數(shù)據(jù)訪問,則會生成數(shù)據(jù)中止異常(Data Abort)。 例如,如果處理器位于 PL0,并且試圖訪問被標記為僅特權模式可訪問的內存區(qū)域,則會生成數(shù)據(jù)中止異常 Data Abort;
針對指令的執(zhí)行來說,PL1 模式下,以定義某存儲器區(qū)域的執(zhí)行權限為:
1、不允許執(zhí)行;
2、在處理器實現(xiàn)了 Large Physical Address Extension 情況下,PL1 不允許執(zhí)行;
3、只能在 PL1 執(zhí)行;
4、特權和非特權模式都可以執(zhí)行;
訪問權限的配置,在 CP15 的 MMU 相關的寄存器,他們的層次結構為:
一些 Cache 基本的內容,比如 “為什么需要 Cache”,“Cache 的組織形式”,“Cache 的映射形式”等,我已經(jīng)在之前的文章《Cache 原理淺析》中敘述得比較清楚了,這里不再贅述,有興趣的同學可以跳轉觀看一下,這里主要是補充一些 ARMv7-A 上的一些細節(jié);
《Cache 原理淺析》可以知道,Cache Line 是 Cache 的最小單位,為了尋找特定 Cache,在 Cache 結構中,ARM 將地址分為了幾段,比如 32bits 的地址總線,ARM 將其分為了 3 段:
最高的一段叫做 Tag,中間的叫 Index,最后叫 Offset;
在一個多路組相關的 Cache 結構中,它的結構如下:
針對這個圖,可以做如下理解:
1、這里的 Line,指的是一個 Cache Line,最小的緩存單位,可能很多字節(jié)(比如,64 Bytes);
2、可以看到,一個 Way,包含了 N 條 Cache Line(這里包含了 4 條),圖中有 4 個 Way,也就是 4-Way associate 的含義;
3、每一個 Way 的同樣位置的 4 條 Cache Line 組成一個 Set,具體分為了多少個 Set,這個要看 Cache 總共有多大,這里畫了 4 個 Set;
4、因為 Cache 進行數(shù)據(jù)緩存,并不是按照 一個地址+一個數(shù)據(jù)(32bits 地址+數(shù)據(jù)),如果按照這樣的結構來緩存,因為這樣效率太低;實際上,它緩存的方式是一組一組來緩存,每一組用一個 Tag 進行表征;所以,每個 Cache Line 就有一個 Tag;
5、Index 用于表征一個 Way 中
5、每個 Way 中,都對 Cache Line 進行編號,有 Index=1,2,3..n(Index=1表示 Cache Line 的編號即 line1,index=2表示 line2), 我們將在不同 way 中,Index 相同的叫成 set。
上面的結構如果用 C 語言來表示的話:
Way = Cache[tag];
Cache Line = Way[Index];
Data = Cache Line[Offset]
舉個真實的例子:
假如我們有一個 Cache Line = 32Bytes,4路 Way 的 組織方式,Cache=32KBytes。
Per Way = (32KBytes / 32KBytes) / 4 Way= 256 Cache Line/Way;
前面說過,一個 Way 中的 Cache Line 是 Index 來索引的,256 個 Cache Line/Way 的話,就要 5 個 bits 來表示;
一個 Cache Line 是 32Bytes,那么就是 8 個 Word,使用 3 個 bits 表示即可;
剩余的高位,作為 Tag 的形式存在:
既然以地址來進行查找 Cache 的,那么我們到底是用虛擬地址還是物理地址呢?三種方式:
1、早期的 ARM 處理器,如 ARM720T 或 ARM926EJ-S 處理器使用虛擬地址提供 Index 和 Tag。 這有一個優(yōu)點,即 CPU 不需要虛擬到物理地址轉換就可以進行緩存查找。 缺點是,每當進行進程切換(虛擬地址映射表發(fā)生改變),Cache 中的虛擬地址就不能再用了,導致性能下降,現(xiàn)在這種方式已經(jīng)淘汰。
2、使用物理地址來查找 Cache(我們叫它 PIPT),那么這么做很明顯解決了第一種的缺點(因為是以物理地址進行 Cache 的,不管映射表怎么變,物理地址不會變)。但是由引入了一個缺點:每次進行查表的時候,都需要到MMU去進行地址轉換,這樣增加了查找cache需要的時間,效率明顯沒有采用虛擬地址的高。注:這種方法,依賴MMU,即MMU關閉,Cache 就必須關閉;
3、是第 1 種和第 2 種的折中處理,將這個查找過程分為兩步,Tag 用物理地址表示,Index 用虛擬地址的,我們叫它 VIPT(Virtually Indexed, Physically Tagged)。那么怎么實現(xiàn)呢?首先,由于 Cache 控制器和 MMU 是兩個獨立模塊,因此通過 MMU 去查找 Tag 和通過 Index 去 Cache 查找 Way 是相互獨立的即可以同時運行。即當用 Index 去 Cache 查找 Set(上面有解釋,即 Index 相同,但處于不同 Way 的一組集合)的同時也在用虛擬地址去 MMU 找物理地址的 Tag,當從cache找到一組set(line[way])的時候(因為只提供了index,因此cache control不知道到底是哪個way,所以返回每個包含index的way),此時MMU中也查到了物理tag,然后再用該物理tag去匹配返回的set,最后獲取到對應的 cache line。常用CPU情況如下。
可能到這里有人會問了,混合使用物理地址和虛擬地址不會有問題嗎?畢竟虛擬地址在進程發(fā)生變動的時候是會不斷變化的。不不不,理論上是不會有問題的,為什么呢?我們知道我們的虛擬映射表了,我們的映射表一般是以4K為一個page,即4k對齊,不管虛擬地址怎么發(fā)生變化,一個page內的偏移是不變的。要尋址一個4k大小我們需要[11:0]共12個bit來提供支持,即在MMU當中,虛擬地址的低12位和物理地址的低12位是相同的。假如我們用的是一個16kb大小,含有4個way,每個line 32bytes的cache,那么通過計算[4:0]用于cache的offset定位,[11:5]則用于cache的index定位。如上所說,虛擬地址和物理地址的[11:0]是相同的,因此index用虛擬地址就不會有影響。但是話又說回來,如果我們的cache大小超過了16k,加入為32kb呢?那么我們以32KB,含有4個way,每個line 32bytes的cache來說,[4:0]用于offset定位,[12:0]用于index定位,那么問題來了,由于虛擬地址和物理地址僅僅是[11:0]相同,那么第13位在發(fā)生切換后,就可能會出現(xiàn)0/1兩個值,意味著一個物理地址可能會同時占用2個cache line,即兩個副本, 這樣就會容易引發(fā)cache一致性的問題。針對于這種cache alias問題,目前的方案是由操作系統(tǒng)來保證,對于同一物理地址在不同進程空間的虛擬地址,他們的虛擬地址的差一定是cache way大小的整數(shù)倍,也就是說他們的第13位一定是相同的。同時已經(jīng)有些cpu廠商在開發(fā)監(jiān)視模塊,試圖在硬件層面解決類似的同步問題。同理對于64kb的cache也采用同樣的方法。
在 Cache 操作策略中可以做出許多不同的選擇。
第一種,CPU讀數(shù)據(jù)時。只有當讀取的時候,發(fā)現(xiàn)cache miss,才從cache中申請一個line去緩存該數(shù)據(jù)。寫的時候,不申請,直接寫入下一級。
第二種,寫和讀時。只要訪問時,不管讀或者寫,發(fā)生了cache miss都去申請一個cache line。
當 Cache miss 的時候,Cache 替換策略:
第一種,Round-robin 或者循環(huán)替換策略;
第二種,Random 替換,Cache 存滿并且出現(xiàn) Cache miss,如果來了一條新的,則隨機找一個 Cache Line 被替換;
第三種,LRU(Least Recently Used) 算法替換,方法如名字,當 Cache 存滿了后,如果來了一條新的,則選擇最近最少使用的被替換;
第一種,Write-Back 模式:寫數(shù)據(jù)時,只向 Cache寫入數(shù)據(jù),并標記 Cache 為 Dirty,然后在合適的時機將數(shù)據(jù)更新到主存;
第二種,Write-Through 模式:寫數(shù)據(jù)時,Cache 和主存都要寫一份;
和 Cache 相關的寄存器控制,首先就是 CP15 的 SCTLR 系統(tǒng)控制寄存器:
bit[2] 是控制 Cache enable 的
bit[12] 是控制指令 Cache enable 的
獲取 Cache 的 Type,刷 Cache 等操作寄存器,等在分析 Linux Kernel 的時候,在對著代碼解讀;
這里需要注意的是,訪問 CP15 協(xié)處理器的寄存器,通過 MRC/MCR 特殊指令;
參考:
linux系統(tǒng)之a(chǎn)rm架構的CPU與Cache_eleven_xiy的博客-CSDN博客
之前寫過 MMU 的一些入門和基礎的分析《初探 MMU》和《ARMv7-A 的 MMU 淺析》,有基于概念掌握和基本入門的一些理解,這里打算在針對 ARMv7-A 的處理器再次稍微深入一點研究一下他的 MMU 和 TLB;
這一版同樣基于 ARM 官方文檔:
的一個是完整版的 ARMv7-A 的處理器架構文檔,第二個是 Cortex-A 系列的 Programmer Guide;
由于有前面的兩篇文章墊底(《初探 MMU》和《ARMv7-A 的 MMU 淺析》),這里就不再介紹一些基礎的內容了,直接單刀直入;
前面知道,MMU 用作虛擬地址和物理地址的相互轉換,是為了能夠給 OS 提供統(tǒng)一視角的虛擬地址空間;
TLB 的作用是作為 MMU 的 Cache,以提高 MMU 的性能,他們之間的關系如下:
1、ARM 處理器發(fā)出地址訪問(虛擬地址),首先過 MMU 地址翻譯單元的 TLB,如果 TLB 命中,那么直接返回真實的物理地址;
2、如果 TLB Miss,那么就要靠 Table Walk 單元去主存中查找表,以獲取物理地址,然后通過 Cache,去訪問;
3、Cache 如果命中,那么直接返回實際物理地址的數(shù)據(jù),否則,也就是最糟糕的情況,會去訪問主存的數(shù)據(jù);
上面的過程呢,軟件要做的,只有配置并放好這個 Transliation Tables,其他的過程,全部是硬件行為;下面馬上仔細的過這部分的細節(jié);
使能 MMU 的參考代碼(因為是 CP15 的系統(tǒng)控制寄存器,所以使用 MRC/MCR 指令):
- MRC p15, 0, R1, c1, C0, 0 ;Read control register
- ORR R1, #0x1 ;Set M bit
- MCR p15, 0,R1,C1, C0,0 ;Write control register and enable MMU
這里要注意的一點是,可能要用到內存屏障指令,因為這里就開啟了 MMU,即將進入虛擬內存的世界,要確保在這之前,流水線干凈,所以執(zhí)行已經(jīng)完畢;
TLB 的全稱是:Translation Lookaside Buffer;從第一節(jié)的那個圖可以看出來,MMU 做 Table Walk 的這個 Transliation Tables 是放到主存中,主存訪問速度很慢(加 Cache 的根本原因),所以,這里每次都去再主存中做 Table Walk,顯然效率非常低,所以,這里就為這個 Table Walk 定制了一個屬于他的 “Cache”,稱之為 TLB;
但是與 真是的 Cache 不一樣(詳見《ARMv7-A 處理器窺探(4) —— Cache》),這個 TLB 是專門緩存 Transliation Tables 的,典型的情況,他的組成如下:
由 VA、ASID、PA、Attributes 組成,即:
VA:虛擬地址;
PA:物理地址;
ASID:Address Space ID;
Attributes:屬性;
TLB 既然扮演的 Transliation Tables Cache 的角色,那么也會有一致性問題,最典型的就是再 OS 中,上下文切換的時候,上一個進程的虛擬地址對應的物理地址表,肯定是和另一個不一樣,導致 TLB 一致性問題;此刻,OS 必須處理這種情況,使得上一個進程的 TLB 對下一個失效,也可以直接通過 CP15 控制寄存器,來 flush 掉 TLB(代價太大);
這里,拋開大物理地址擴展和 section 和 supersection 的分析,暫時就看最最常用的兩段查找;兩段頁表查找,我們稱第一級頁表為 L1,第二級為 L2;
前面知道,軟件需要負責構建這個虛擬地址到物理地址的轉換表:Transliation Tables,當軟件構件完畢這個表后,只需要告訴硬件,這個 Transliation Tables 放到了那個首地址即可,這個配置通過寫 ARM 的 TTBR 寄存器實現(xiàn)(Translation Table Base Address );這里其實有兩個 TTRB 寄存器,分別叫 TTBR0 和 TTBR1,為啥兩個,后面解釋;
和這個 TTBR0、TTBR1 勾肩搭背的,還有一個 TTBCR 寄存器,他們直接什么關系呢,看寄存器說明:
TTBCR:
PD0 和 PD1 是和 Security Extensions 相關的,不管他;
EAE 是和 Large Physical Address Extension 相關的,不管他;
主要關注這里的 N[2:0],指示TTBR0頁表基址寄存器基址位寬,同時指示使用 TTBR0 還是 TTBR1 作為頁表基址寄存器,以及 TTBR0 頁表尺寸:
如果 N = 0 的話,則在做 Table Walk 的時候使用 TTBR0 指定的基地址作為 Transliation Tables 入口的地址;
如果 N > 0 的話:指示TTBR0頁表基址寄存器基址位寬,同時指示使用 TTBR0 還是 TTBR1 作為頁表基址寄存器,以及 TTBR0 頁表尺寸;
- N==0,使用 TTBR0。
- N>0,如果虛擬地址[31:32-N]為0,則使用 TTBR0;其他情況使用TTRB1。這種情況下,N 指示了TTBR1的頁表地址,也指示了 TTBR0 的頁表大小。
- TTRB0的頁表大小由TTBCR.N控制,TTRB1的頁表大小為16KB。
我換句話來說,當 N>0 的時候,比如 N=1,那么按照這種說法,VA [31:31] 也就是 VA 的 bit[31] 為 0 的時候,使用 TTBR0 否則使用 TTRB1,按照地址空間來劃分,即,32bits 地址,當最高位為 0,即虛擬地址為 0x0000_0000 ~ 0x7FFF_FFFF 這個區(qū)間的時候,使用 TTBR0 作為 Transliation Tables 入口的地址,從 0x8000_000 ~ 0xFFFF_FFFF 的虛擬地址空間,使用 TTBR1;
ARM 官方舉了個例子,當 TTBCR.N=3'b111 的時候,VA [31:25] 全部為 0 的時候,使用 TTBR0,按照地址空間來劃分就是,虛擬地址為:0x0000_0000 ~ 0x01FF_FFFF 這段區(qū)間使用 TTBR0 作為 Transliation Tables 入口的地址;
0x0200_0000 ~ 0xFFFF_FFFF 的虛擬地址空間,使用 TTBR1;
OK,現(xiàn)在可以理解為,配置 TTBCR.N 這個值,可以實現(xiàn)將虛擬地址切割成為兩部分,一部分使用 TTBR0 指定的 Transliation Tables 進行 Table Walk,另一部分使用 TTBR1 指定的 Transliation Tables 進行 Table Walk,這個有什么好處呢?比如,內核的頁表,是不會改變的,而進程上下文的頁表是會改變的,有了這個的話,就可以考慮用一個 TTBR 來專門為內核服務,另一個 TTBR 為進程服務,這樣避免進程和內核使用同一個頁表,每次都要進行內核頁表的拷貝;
由于 TTBCR 是 CP15 的寄存器,訪問 TTBCR 的指令為:
- MRC p15, 0, <Rt>, c2, c0, 2 ; Read TTBCR into Rt
- MCR p15, 0, <Rt>, c2, c0, 2 ; Write RT to TTBCR
上面說了 TTBCR,下面來看 TTRB0、TTRB1 寄存器描述:
TTBR0
在帶有多核處理器擴展的情況下 TTBR0 由一個可變的長度構成 Transliation Tables Base Address,這個 x 就是上面的 (14 - (TTBCR.N));
Bits[31:x]:x=(14-(TTBCR.N))。一級頁表地址;
Bits[x-1:7]:Reserved;
NOS:Not Outer Shareable bit,指示了做 Table walk 的那個內存的屬性,是 Outer Shareable 還是 Inner Shareable.
- 0 Outer Shareable.
- 1 Inner Shareable.
TTBR0.S == 0 時,該bit無效;
S:Shareable bit. 指示內存共享屬性與頁表轉換的關系;
- 0 Non-shareable.
- 1 Shareable.
RNG:Region bits,指示 Outer Cache 屬性與頁表轉換的關系;
- 0b00 Normal memory, Outer Non-cacheable.
- 0b01 Normal memory, Outer Write-Back Write-Allocate Cacheable.
- 0b10 Normal memory, Outer Write-Through Cacheable.
- 0b11 Normal memory, Outer Write-Back no Write-Allocate Cacheable.
IRGN[6,0]:Inner region bits,指示 Inner Cache 屬性與頁表轉換的關系;
- 0b00 Normal memory, Inner Non-cacheable.
- 0b01 Normal memory, Inner Write-Back Write-Allocate Cacheable.
- 0b10 Normal memory, Inner Write-Through Cacheable.
- 0b11 Normal memory, Inner Write-Back no Write-Allocate Cacheable.
訪問 TTBR0 的指令為:
- MRC p15, 0, <Rt>, c2, c0, 0 ; Read 32-bit TTBR0 into Rt
- MCR p15, 0, <Rt>, c2, c0, 0 ; Write Rt to 32-bit TTBR0
TTBR1
它的位域和 TTBR0 幾乎一樣,唯一不一樣的地方在于,配置的地址區(qū)間在于 bit[31:14],這意味著,配置進 TTBR1 的 Transliation Tables Base Address 的物理地址,必須 16KB 對齊;
訪問 TTBR1 的指令為:
- MRC p15, 0, <Rt>, c2, c0, 1 ; Read 32-bit TTBR1 into Rt
- MCR p15, 0, <Rt>, c2, c0, 1 ; Write Rt to 32-bit TTBR1
現(xiàn)在我們知道了合理的配置 TTBCR/TTBR0/TTBR1 可以分配并指定 Transliation Tables,而這個 Transliation Tables 位于內存中,用作 MMU 來做 Table Walk;那么接下來我們需要知道頁表的結構,這樣我們才能夠在內存中創(chuàng)建頁表,并將頁表配置給 TTBR 寄存器,完成 MMU 的配置;
不考慮大地址擴展和 SuperSection 以及 Section 的情況下,針對 Transliation Tables,ARMv7-A 的手冊給出了如下的圖解
圖中,我們暫時只考慮 Page Table 的情況,即紅色部分(其余的可以照著推);
藍色的部分,可以理解為之前寄存器里面配置的那個 N 值;這里為了說明情況,我們暫時將 N 定為 0;
我們先暫時不管使用 TTBR0 還是 TTBR1,其實過程是一樣的;此刻當 N = 0 的時候,一級頁表以虛擬地址(后面簡稱 VA,即 Virtual Address)VA[31:20] 作為 L1 Index,一共 12bits,最大能夠表征 4K 的 Index:
每個入口是 4 Bytes 也就是一個 Word,32bits 的入口,L1 Index 從 0~4095,一共 4K,在內存上,每個入口 4Bytes,那么 L1 頁表占用內存 4K x 4 Bytes = 16KB;
每一個入口是什么樣子的呢,我們放大來看:
可以看到,這個入口,根據(jù)不同的配置,內容有所區(qū)別,一共有 4 種類型,這 4 種類型,通過 32bits 的尾部 2 bits 來區(qū)分,即,綠色部分(Section 和 SuperSection 的區(qū)分,還靠 bit[18]);
這里我們暫時不關心 Section 和 SuperSection,關注于紅色部分和那個 Fault;
Level 2 Descriptor Base Address:指向的是 L2 頁表的物理地址的基地址;可以看到他是 bit[31:10],是 1KB 邊界對齊的;
這個 Domain 指的是 ARM 支持將內存標記為最多 16 個 domain,并以 Domain ID 作為區(qū)分,每個 Domain 可以支持配置成為不同的訪問權限(通過配置 CP15 的 C3 的 Domain Access Control Register (DACR) 寄存器):
配置指令為:
- MRC p15, 0, <Rt>, c3, c0, 0 ; Read DACR into Rt
- MCR p15, 0, <Rt>, c3, c0, 0 ; Write Rt to DACR
針對這個 DACR 寄存器,ARM 官方建議配置成為 Client;
The use of domains is deprecated in the ARMv7 architecture, and will eventually be removed,
but in order for access permissions to be enforced, it is still necessary to assign a domain number
to a section and to ensure that the permission bits for that domain are set to client. Typically, you
would set all domain ID fields to 0 and set all fields in the DACR to 'Client’.
介紹完 L1 Address Translation 后,下面是二級頁表!與 L1 頁表不一樣,二級頁表不和 N 值掛鉤,它直接采用 VA[19:12] 作為 L2 Index 索引,一共 8 bits,最大能夠表征 256 的 L2 Index;
加入 L2 頁表后結合 L1,通過一個給定的 VA 進行索引的第二步為(圖中表示的 N 值為 0):
這樣,一個 VA 通過高地址部分[31:20] 索引到了 L1,再從 L1 指向的 L2 加上 VA[19:12] 作為 L2 Index,索引到 L2 表的固定位置;
L2 也是每條由 4 Bytes 構成,即一個 32bits 的數(shù),那么一個 L2 表大小為 256 x 4 Bytes = 1024 Bytes = 1KB;一共有 4096 個這樣的 L2,那么 L2 表總的大小為:4096 x 1KB = 4MB;
我們放大每一條 L2 的入口:
我們只關心紅色部分!可以看到,這個 Small Page Base Address 有 bit[31:12] 也就是 4KB 邊界對齊!接下來我們看剩余幾個位的含義:
AP/APX:Access Permission 即訪問權限,每個內存區(qū)域 都有自己的權限,不符合訪問權限的 內存訪問都會引發(fā) 異常。如果是 數(shù)據(jù)訪問 則引發(fā) 數(shù)據(jù)異常。如果是 指令訪問,且該指令在執(zhí)行前沒有被 flush,將引發(fā) 預取指異常。引發(fā)的 異常原因將會被設置在 CP15 的 the fault address and fault status registers;
內存區(qū)域類型 可以通過 TEX字段、C字段 和 B字段 來進行設置:
XN:指的是 Execute Never,不允許執(zhí)行,如果往這里取地址執(zhí)行,那么會導致異常發(fā)生;通常,Device memory 類型的區(qū)域會配置成為 XN;
S:指的是是否具有 Shareable 屬性;
nG:non-Global,這個標記告訴 MMU,這個頁表是否是一個全局的,什么意思呢?看下面:
當 nG 為 0 的時候,說明此區(qū)域是全局可見的,換句話來說,就是任何時候都生效;
當 nG 為 1 的時候,說明此區(qū)域不是全局的,要聯(lián)合這個 ASID 來確認;
每一個 nG=1 的區(qū)域,都會和 ASID 來關聯(lián),ASID (Address Space Identifier),這代表,TLB 可以存在多個不同進程的頁表緩存,后面說 ASID 的時候會仔細說;
自此,L1/L2 分析完畢,那么整個 Table Walk 的流程為:
VA 的 4K 頁內偏移,直接對應到 PA 的 4KB 頁內偏移;
通常情況下,在使用 Cortex-A 系列處理器的時候,典型場景是跑多任務 OS;每一個任務(或者成為應用),都有它獨立的虛擬地址空間,以及他的獨立的 Translation Table;但是對于 OS 來說,Kernel 的 Translation Tables 其實是固定的,只是進程之間的 Translation Tables 不一樣而已;
當一個進程啟動的時候,OS 負責為他 code 和 data 段建立映射表(Translation Tables);當進程調用諸如 malloc 之類分配內存的行為,OS 負責修改 Translation Tables(Linux 中,實際訪問分配的內存的時候,才去修改頁表),進程生命周期消亡,OS 負責回收它的資源和頁表,并可以為下一個新的進程提供資源;每一個進程都有自己的獨立的頁表,這便可以保證進程之間不會相互干擾;
在操作系統(tǒng)中,多進程是一種常態(tài)。那么多進程 的情況下,每次切換進程都需要進行 TLB 清理。這樣會導致切換的效率變低。為了解決問題,TLB 引入了 ASID(Address Space ID) 。ASID 的范圍是 0-255。ASID 由操作系統(tǒng)分配,當前進程的ASID值 被寫在 ASID 寄存器 (使用CP15 c3訪問)。TLB 在更新頁表項時也會將 ASID 寫入 TLB。
如果設置了如果 當前進程的ASID,那么 MMU 在查找 TLB 時, 只會查找 TLB 中具有 相同ASID值 的 TLB行。且在切換進程是,TLB 中被設置了 ASID 的 TLB行 不會被清理掉,當下次切換回來的時候還在。所以ASID 的出現(xiàn)使得切換進程時不需要清理 TLB 中的所有數(shù)據(jù),可以大大減少切換開銷。
有了這個 ASID + nG 的機制,那么 TLB 中就可以緩存不同進程的頁表,不用每次都去 flush TLB,導致性能的損失:
前面我們說了 TTBR0、TTBR1 是根據(jù) TTBCR.N 來進行劃分的,典型場景下 OS 跑多任務,如果處理器只能夠支持一個 TTBR 的話,也就意味著用戶空間和內核空間使用同一個 TTBR,由于內核空間的 code 和 data 幾乎是不變的,但是多任務的用戶空間都是不一樣的,這樣就會存在兩個問題:
1、多個任務的頁表里面,都有同樣一部分內核映射的拷貝副本;
2、要修改內核映射的時候,所有任務的頁表都要修改;
加入兩個 TTBR 的原因,是因為希望內核和用戶空間使用兩套 TTBR,這樣就可以避免上面的尷尬;內核空間固定使用一組,用戶空間不斷的切換(比如,配合 TTBR0 + ASID 進行性能的提升)
參考文獻:
https://www.jianshu.com/p/ef1e93e9d65b
聯(lián)系客服