aarch64系統級體系架構之異常級別
- 1.簡述
- 2.樹莓派啟動深度解析
- 3.不同異常級別需要注意的問題
1.簡述
系統的異常級別對於arm芯片來說非常的重要,對於操作系統層面上來說,理解芯片的體系架構,將很容易的進入狀態,隨心所欲的去玩轉芯片,對於做應用來說,熟悉芯片的體系架構,可以解決非常棘手的問題,比如系統的安全還有就是實時性響應問題。比如我們的手機指紋加密數據,實際上是在安全模式下的,此時對於運行在非安全模式下的操作系統,其實是獲取不到指紋的數據的,只是處理安全模式下發送過來的結果,類似的還有支付安全。
對於armv8異常級別,其實是一個很大的話題,但是深入理解之后,就會發現這時一個很有意思的東西。看過盜夢空間的電影都知道,夢有好多層,但是那一層是真實的,那一層是夢境,真實到夢境如何切換,夢境到真實如何切換,這真的是不識廬山真面目,只緣生在此山中
。
本文簡單介紹一下樹莓派啟動的異常級別,如何從不同的exception level進行切換,同時啟動的時候如何指定exception level,大體上去理解異常級別。
獲得當前 EL
AArch64 架構下 CurrentEL
用 64 位表示,其中第 [3:2] 位表示 EL,[63:4] 和 [1:0] 都是保留位。
EL | Meaning |
---|---|
0b00 | EL0 |
0b01 | EL1 |
0b10 | EL2 |
0b11 | EL3 |
通過下面的代碼能夠獲得當前的 EL:
// Move the contents of a PSR to a general-purpose register // 將 CurrentEL 值寫入通用寄存器 x0 mrs x0, CurrentEL // Logical Shift Right // 將 Xt 值右移 2 位 lsr x0, x0, #2
比如獲取到的值是 0b0100
, 因為 [1:0] 2位是保留位,總是為0,所以右移 2 位就得到了上表中的 0b01
EL。
所以能創建如下的方法去獲取當前 EL:
.global get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret // 返回到 X30 寄存器
切換 EL
-
ARM 架構下有如下3種類型的異常:
- Interrupts: 中斷。這里的中斷是指硬件層面的中斷。通常由外部硬件發起中斷。
- Aborts: 中止。這個就是軟件層面的異常了。比如代碼中訪問了不存在的內存地址,進行了除0操作(指令執行錯誤)等問題。
- Reset: 復位。復位被視為實現最高異常級別的特殊變量。這是引發異常時 ARM 處理器跳轉到的指令的位置。 該向量使用 IMPLEMENTATION DEFINED 地址。
RVBAR_ELn
包含此變量的地址,其中 n 是已實現的最高異常級別的編號。所有內核都有一個復位輸入,並在復位后立即執行復位異常。它是最高優先級的異常,無法屏蔽。處理器上電后,此異常用於在內核上執行初始化的代碼。
-
除了運行時程序出現問題產生異常外,也有一些指令能夠產生異常,通常執行這些指令是為了從運行於更高權限級別的軟件中請求服務:
svc
: Supervisor Call 指令使用戶模式程序可以請求 OS 服務hvc
: Hypervisor Call 指令使來賓 OS 可以請求系統管理程序服務。smc
: 通過 Secure monitor Call 指令,普通世界可以請求安全世界服務。(security model 參考)
如果異常是由於在 EL0 執行指令而生成的,則會將其視為 EL1 的異常。如果是在任何其他 EL 執行指令導致生成異常,則異常級別保持不變。
相關寄存器
當處理異常時,會涉及幾個寄存器的操作:
- 當前正在執行的指令地址(PC 寄存器)會被存儲在 ELR_ELn(Exception link register) 中
- 當前處理器的狀態(PSTATE)被存儲在 SPSR_ELn(Saved Program Status Register)
- 異常處理程序被執行。異常處理程序可以修改 ELR 和 SPSR
- 異常處理程序執行 eret 指令。這個指令會從 SPSR_Eln 寄存器恢復處理器的狀態,並且恢復 ELE_Eln 中儲存的指令的執行
由於異常處理程序可以修改 ELR_ELn
和 SPSR_ELn
寄存器,所以異常處理程序能夠間接的修改 EL 等參數,達到切換 EL 的目的。
切換到 EL1
回到樹莓派OS這邊,通電之后,處理器默認是在最高級別的 EL 運行的,也就是 EL3。現在我希望樹莓派OS 啟動之后切換到 EL1 執行(如同 Linux 和 Windows 都是在EL1運行)。通過配置一些系統寄存器,然后調用 eret
指令觸發處理器去執行狀態重讀取就能達成目的。
master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret
配置 SCTLR_EL1
System Control Register
sctlr_eln 寄存器被用來配置處理器的不同參數。存在 sctlr_el1、 sctlr_el2 和 sctlr_el3 分別對應 EL1、 EL2 和 EL3 的寄存器。
sctlr_el1 寄存器能夠配置 EL0 和 EL1 級別的內存等配置。通過修改 sctlr_el1 某些位的值能達到配置處理器在 EL0 和 EL1 級別運行時的行為。
ldr x0, =SCTLR_VALUE_MMU_DISABLED
msr sctlr_el1, x0
#define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) // 保留位賦值1 #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) // EL1 采用小端字節序 #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) // EL0 采用小端字節序 #define SCTLR_I_CACHE_DISABLED (0 << 12) // 禁用指令緩存 #define SCTLR_D_CACHE_DISABLED (0 << 2) // 禁用數據緩存 #define SCTLR_MMU_DISABLED (0 << 0) // 禁用內存管理單元 #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED)
配置 HCR_EL2
Hypervisor Configuration Register
HCR_EL2 寄存器提供了虛擬化的配置,包括定義是否將各種操作限制在EL2中。因為只有 EL2 支持 Hypervisor,所以只存在一個 HCR_EL2 寄存器。
#define HCR_RW (1 << 31) // EL1 的 Execution State 為 AArch64 #define HCR_VALUE HCR_RW
ldr x0, =HCR_VALUE
msr hcr_el2, x0
配置 SCR_EL3
Secure Configuration Register
SCR_EL3 寄存器定義當前安全狀態的配置:
- EL0,EL1 和 EL2 的安全狀態為 Secure 或 Non-Secure
- EL2 的 Execution State
#define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) // EL2 的Execution State 為 AArch64 #define SCR_NS (1 << 0) // 安全狀態為 Non-secure #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS)
ldr x0, =SCR_VALUE
msr scr_el3, x0
配置 SPSR_EL3
Saved Program Status Register
EL3 發生異常時,SPSR_EL3 寄存器用來保存處理器的狀態。這里因為樹莓派OS啟動之后是在 EL3 運行,所以通過修改 SPSR_EL3 的值來修改處理器的運行狀態。
7 << 6
意味着將 [8:7] 3 個位都置 1:
#define SPSR_MASK_ALL (7 << 6)
- bit[8]: SError interrupt mask
- bit[7]: IRQ interrupt mask
- bit[6]: FIQ interrupt mask
設置 Exception level 和 selected Stack Pointer 為 EL1h:
#define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h)
位 | 含義 |
---|---|
0b0000 | EL0t |
0b0100 | EL1t |
0b0101 (5 << 0 ) |
EL1h |
0b1000 | EL2t |
0b1001 | EL2h |
0b1100 | EL3t |
0b1101 | EL3h |
ldr x0, =SPSR_VALUE
msr spsr_el3, x0
將 SPSR_EL3 的第 [3:0] 3 個位, 置為 EL1h
后,在執行 eret
指令后,處理器的狀態會從 SPSR_EL3 中恢復,也就讓處理器的 EL 切換到了 EL1。
配置 ELR_EL3
Exception Link Register (EL3)
在 EL3 進行異常處理時,ELR_EL3 寄存器將用來保留要返回的地址。
adr x0, el1_entry // 將 el1_entry label 地址存到 x0 msr elr_el3, x0 // 將 x0 值存到 elr_el3
先將 el1_entry
符號地址存到 elr_el3
,在執行 eret
指令之后,處理器就將從 elr_el3
寄存器讀取符號去恢復執行,也就間接的讓處理器執行 el1_entry
。
通過配置上述系統寄存器,然后調用 eret
觸發處理器的執行狀態的重恢復,就能將異常級別從 EL3 切換到 EL1 了。
2.樹莓派啟動深度解析
樹莓派的啟動流程,我想簡單敘述一下,就是上電之后,啟動了GPU,然后通過GPU去啟動arm的核,然后就是讀取配置文件,設置ddr等等。如果sd卡里有kernel8.img文件,那這個就是Linux內核執行的程序。此時,Linux就執行起來了。
對於rt-thread來說,情況是一樣的,可以在config.txt里寫下如此的文件
kernel=kernel8.img kernel_addr=0x80000 enable_uart=1
這就告訴樹莓派,需要啟動的固件名字是kernel8.img
,入口地址0x80000
。
其實這並不是芯片上電后執行的第一個程序,還運行了一個叫start.elf的文件,該文件會加載kernel8.img。通過測試得知,樹莓派其實在kernel8.img的入口的第一條指令是在el2下的。關於el3,el2,el1,el0可以看下面的圖進行理解。
應用程序運行在EL0上,此時可以訪問的寄存器很有限,比如我們安卓手機安裝的app,其實都是運行在EL0的。而EL1是運行Kernel的,比如Linux的或者是rt-thread。
到了EL2就是提供了虛擬化的實現,這一層涉及到虛擬化,在服務器上用的比較多。
然后就是EL3,這個比較厲害,權限比較大,基本上可以訪問所有寄存器,而且電源管理,也在里面。另外這個就類似於一個電梯,打通了安全與非安全的通道。
樹莓派啟動內核在EL2里面,那么我們知道操作系統運行在EL1的非安全模式下,安全模式是對於安全應用場景的,這里不做考慮,但是如果要訪問GIC的組,一般是在安全模式。
目的就是從EL2->EL1。
// enable AArch64 in EL1 mov x0, #(1 << 31) // AArch64 orr x0, x0, #(1 << 1) // SWIO hardwired on Pi3 msr hcr_el2, x0 mrs x0, hcr_el2 // change execution level to EL1 mov x2, #0x3c4 msr spsr_el2, x2 // 1111000100 adr x2, .L__in_el1 msr elr_el2, x2 eret // exception return. from EL2. continue from .L__in_el1
主要就是使能el1在64位模式下運行,然后配置系統從EL2->>EL1,采用的是eret指令,該指令會將pc指針指向elr_el2對應的地址。
如果我們想要樹莓派在el3上運行,可以采用MVC指令進行模式切換。
svc,hvc,smc指令切換,對EL1~3有三種不同的中斷向量。
不想進行指令切換,最簡單的辦法,就是編譯一個鏈接地址為0的固件,在config.txt中寫下
armstub=kernel8.img
此時,系統從el3運行,並且起始地址為0。和芯片上電執行第一條指令模式類似。
3.不同異常級別需要注意的問題
既然涉及到異常級別,那就不得不說一下使用異常級別需要注意的問題了。安全和非安全這是物理隔離的,但是異常級別卻是需要進行切換的。比如我們從非安全到安全,是不能直接切換過去的,需要借助el3這個電梯,可以借助這個過去。
然后就是你在el1上訪問某些寄存器的時候,突然系統hard fault,這時就要看aarch64的芯片手冊了,看這個寄存器是在那個異常級別下可以訪問的。有些寄存器在不對應的異常級別,讀為零,寫無效。比如GIC的某些寄存器。
異常級別對於芯片的體系架構非常重要,做底層開發,離不開體系架構知識,做上層開發理解芯片體系架構更好,設計更加符合芯片設計的產品,做更加性能優化的產品是非常好的。