ARM Cortex-A 编程手册学习笔记
閑話
從前都在X86上分析內核,做開發、trouble shooting,對于其他架構了解較少,對于新架構的學習,甚至還有些抵觸,這次趁分析問題的機會,順便學習了一下ARM架構的基礎知識,權當筆記。
這里主要是AArch32架構(即32位,后面就都簡寫成ARM了),相對比較簡單,入門必備。
ARM處理器模式
在引入安全擴展之前,ARM有7中處理器模式,其中6種是特權模式,剩下一種為用戶程序運行的非特權模式。特權模式下,可以做一些user模式下不能做的操作,比如mmu配置和cache操作。
| User (USR) | Mode in which most programs and applications run | Unprivileged |
| FIQ | Entered on an FIQ interrupt exception IRQ Entered on an IRQ interrupt exception | ? |
| Supervisor (SVC) | Entered on reset or when a Supervisor Call instruction (SVC) | ? |
| is executed | ? | ? |
| Abort (ABT) | Entered on a memory access exception Undef (UND) Entered when an undefined instruction executed | ? |
| System (SYS) | Mode in which the OS runs, sharing the register view | ? |
TrustZone Security Extensions引入了獨立于模式的兩種安全狀態,和一種新的Monitor模式。如此就有8種CPU模式了。
TrustZone Security Extensions的環境中,通過將所有的硬件和軟件資源按設備來劃分,來提升系統安全性。 在CPU處于Normal (Non-secure) state,其不能訪問在Secure state下分配的內存。
當前處理器所處模式,由CPSR(當前程序狀態寄存器)寄存器的值決定,改變cpu模式可以通過特權軟件顯示設置該寄存器,也可以由異常觸發(發生異常后,CPU就會自動切換到對應的模式)。
寄存器
ARM架構提供16個32位的通用寄存器(R0-R15),R0-R14可用作普通的數據存儲。
R15為PC寄存器,修改R15可以改變程序的執行流程。PC值指向當前執行指令的地址加8處,PC是有讀寫限制的。
R14也稱LR(Link register),通常用于保存當前函數的返回地址(上一級函數),當其空閑時,也可以用作普通寄存器用。每種模式下都有自己獨立(物理上)的R14。
R13也稱SP,即用于保存堆棧的棧頂指針,當其空閑時,也可以用作普通寄存器用。每一種異常模式都有其自己獨立的r13,它通常指向異常模式所專用的堆棧。當ARM進入異常模式的時候,程序就可以把一般通用寄存器壓入堆棧,返回時再出棧,保證了各種模式下程序的狀態的完整性。
程序也可以訪問CPSR,SPSR是前一個執行模式下CPSR的副本。
雖然軟件可以訪問這些寄存器,但是不同模式下,部分寄存器實際對應的物理存儲位置可能不同,也就是說,不同模式下,部分寄存器(除R0-7和R15外)實際物理上是不同(雖然對軟件來說是透明的,軟件看到的是相同的邏輯上的寄存器),這些寄存器只能在相應模式下才能訪問。
Program Status Registers。CPSR用于存儲:flags、當前CPU模式、中斷禁用標記、當前CPU狀態等。在user模式下,CPSR對應的寄存器為APSR(限制版本的CPSR)。
Coprocessor 15
CP15,系統控制協處理器,用于控制Core的需要特性,包括c0-c15主要的32位寄存器,這些寄存器通常也通過名稱訪問,如CP15.SCTLR
Cache
ARM中的常見Cache架構如下:
cache問題
讓程序執行時間變得不可知。
對于實時性要求高的系統來說有點不可接受。
外設需要用up-to-date的數據,需要cache控制。
其他
tag占物理空間,但不計算在cache size內
invalidate操作:丟掉cache。
clean操作:將臟數據刷入內存,保證一致。
Point of coherency and unification
For set/way based clean and invalidate, the operation is performed on a specific level of cache.For operations that use a virtual address, the architecture defines two conceptual points:
Point of Coherency (PoC)
For a particular address, the PoC is the point at which all blocks, for example,cores, DSPs, or DMA engines, that can access memory are guaranteed to see thesame copy of a memory location. Typically, this will be the main external systemmemory.
Point of Unification (PoU)
The PoU for a core is the point at which the instruction and data caches of the coreare guaranteed to see the same copy of a memory location. For example, a unifiedlevel 2 cache would be the point of unification in a system with Harvard level 1caches and a TLB for cacheing translation table entries. If no external cache ispresent, main memory would be the Point of unification.
ARM64中包含如下幾種類型的異常:
異常級別(EL)
ARM中,異常由級別之分,具體如下圖所示,只要關注:
普通的用戶程序處于EL0,級別最低
內核處于EL1,HyperV處于EL2,EL1-3屬于特權級別。
異常處理
Arm中的異常處理過程與X86比較相似,同樣包括硬件自動完成部分和軟件部分,同樣需要設置中斷向量,保存上下文,不同的異常類型的處理方式可能有細微差別。 這里不詳述了。
需要關注:用戶態(EL0)不能處理異常,當異常發生在用戶態時,異常級別(EL)會發生切換,默認切換到EL1(內核態)。
中斷向量表
Arm64架構中的中斷向量表有點特別(相對于X86來說~),包含16個entry,這16個entry分為4組,每組包含4個entry,每組中的4個entry分別對應4種類型的異常:
4個組的分類根據發生異常時是否發生異常級別切換、和使用的堆棧指針來區別。分別對應于如下4組:
代碼分析
## 中斷向量表
Linux內核中,中斷向量表實現在entry.S文件中,代碼如下:
/** Exception vectors.*/.align 11/*el1代表內核態,el0代表用戶態*/ENTRY(vectors)ventry el1_sync_invalid // Synchronous EL1t ventry el1_irq_invalid // IRQ EL1tventry el1_fiq_invalid // FIQ EL1t/*內核態System Error ,使用SP_EL0(用戶態棧)*/ventry el1_error_invalid // Error EL1tventry el1_sync // Synchronous EL1hventry el1_irq // IRQ EL1hventry el1_fiq_invalid // FIQ EL1h/*內核態System Error ,使用SP_EL1(內核態棧)*/ventry el1_error_invalid // Error EL1hventry el0_sync // Synchronous 64-bit EL0ventry el0_irq // IRQ 64-bit EL0ventry el0_fiq_invalid // FIQ 64-bit EL0/*用戶態System Error ,使用SP_EL1(內核態棧)*/ventry el0_error_invalid // Error 64-bit EL0#ifdef CONFIG_COMPATventry el0_sync_compat // Synchronous 32-bit EL0ventry el0_irq_compat // IRQ 32-bit EL0ventry el0_fiq_invalid_compat // FIQ 32-bit EL0ventry el0_error_invalid_compat // Error 32-bit EL0#elseventry el0_sync_invalid // Synchronous 32-bit EL0ventry el0_irq_invalid // IRQ 32-bit EL0ventry el0_fiq_invalid // FIQ 32-bit EL0ventry el0_error_invalid // Error 32-bit EL0#endifEND(vectors)可以明顯看出分組和分類的情況。
invalid類處理
帶invalid后綴的向量都是Linux做未做進一步處理的向量,默認都會進入bad_mode()流程,說明這類異常Linux內核無法處理,只能上報給用戶進程(用戶態,sigkill或sigbus信號)或die(內核態)
帶invalid后綴的向量最終都調用了inv_entry,inv_entry實現如下:
/** Invalid mode handlers*//*Invalid類異常都在這里處理,統一調用bad_mode函數*/ .macro inv_entry, el, reason, regsize = 64 kernel_entry el, \regsize /*傳入bad_mode的三個參數*/ mov x0, sp /*reason由上一級傳入*/ mov x1, #\reason /*esr_el1是EL1(內核態)級的ESR(異常狀態寄存器),用于記錄異常的詳細信息,具體內容解析需要參考硬件手冊*/ mrs x2, esr_el1 /*調用bad_mode函數*/ b bad_mode .endm調用bad_mode,是C函數,通知用戶態進程或者panic。
/** bad_mode handles the impossible case in the exception vector.*/ asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr) {siginfo_t info;/*獲取異常時的PC指針*/void __user *pc = (void __user *)instruction_pointer(regs);console_verbose();/*打印異常信息,messages中可以看到。*/pr_crit("Bad mode in %s handler detected, code 0x%08x -- %s\n",handler[reason], esr, esr_get_class_string(esr));/*打印寄存器內容*/__show_regs(regs);/*如果發生在用戶態,需要向其發送信號,這種情況下,發送SIGILL信號,所以就不會有core文件產生了*/info.si_signo = SIGILL;info.si_errno = 0;info.si_code = ILL_ILLOPC;info.si_addr = pc;/*給用戶態進程發生信號,或者die然后panic*/arm64_notify_die("Oops - bad mode", regs, &info, 0); }arm64_notify_die:
void arm64_notify_die(const char *str, struct pt_regs *regs,struct siginfo *info, int err) {/*如果發生異常的上下文處于用戶態,則給相應的用戶態進程發送信號*/if (user_mode(regs)) {current->thread.fault_address = 0;current->thread.fault_code = err;force_sig_info(info->si_signo, info, current);} else {/*如果是內核態,則直接die,最終會panic*/die(str, regs, err);} }IRQ中斷處理
場景的場景中(不考慮EL2和EL3),IRQ處理分兩種情況:用戶態發生的中斷和內核態發生的中斷,相應的中斷處理接口分別為:
el0_sync el1_sync相應代碼如下:
el1_sync:
.align 6 el1_irq:/*保存中斷上下文*/kernel_entry 1enable_dbg #ifdef CONFIG_TRACE_IRQFLAGSbl trace_hardirqs_off #endif/*調用中斷處理默認函數*/irq_handler/*如果支持搶占,處理稍復雜*/ #ifdef CONFIG_PREEMPTget_thread_info tskldr w24, [tsk, #TI_PREEMPT] // get preempt countcbnz w24, 1f // preempt count != 0ldr x0, [tsk, #TI_FLAGS] // get flagstbz x0, #TIF_NEED_RESCHED, 1f // needs rescheduling?bl el1_preempt 1: #endif #ifdef CONFIG_TRACE_IRQFLAGSbl trace_hardirqs_on #endif/*恢復上下文*/kernel_exit 1 ENDPROC(el1_irq)代碼非常簡單,主要就是調用了irq_handler()函數,不做深入解析了,有興趣可以自己再看看代碼。
el0_sync處理類似,主要區別在于:其涉及用戶態和內核態的上下文切換和恢復。
.align 6 el0_irq:kernel_entry 0 el0_irq_naked:enable_dbg #ifdef CONFIG_TRACE_IRQFLAGSbl trace_hardirqs_off #endif/*退出用戶上下文*/ct_user_exitirq_handler#ifdef CONFIG_TRACE_IRQFLAGSbl trace_hardirqs_on #endif/*返回用戶態*/b ret_to_user ENDPROC(el0_irq)原文地址: https://happyseeker.github.io/kernel/2016/03/05/ARM-Cortex-A-programming-mannual-notes.html
總結
以上是生活随笔為你收集整理的ARM Cortex-A 编程手册学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Arm64中的异常处理
- 下一篇: ftrace、kpatch、system