文章目录
- [1. 前言](#1. 前言)
- [2. ARM64 中断处理流程扼要](#2. ARM64 中断处理流程扼要)
-
- [2.1 ARM64 的 CPU 异常等级 和 运行态](#2.1 ARM64 的 CPU 异常等级 和 运行态)
- [2.2 ARM64 中断向量表配置 和 中断处理流程](#2.2 ARM64 中断向量表配置 和 中断处理流程)
- [2.3 其它:RESET 异常 和 系统调用](#2.3 其它:RESET 异常 和 系统调用)
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. ARM64 中断处理流程扼要
2.1 ARM64 的 CPU 异常等级 和 运行态
ARM64 没有像 ARM32 的那么多个 CPU 模式,ARM64 CPU 当前位于 EL0~EL3 的异常等级之一,这有点类似于 x86 架构下的 RING0~RING3。ARM64 CPU 的 ELx 异常等级的 x 越大,权限越高。

- EL0:非特权模式,通常用于运行应用程序
- EL1:特权模式,通常用于运行 OS 内核
- EL2:Non-secure 虚拟化支持
- EL3:Secure monitor 模式,用于 Non-secure 和 Secure 状态切换
ARM64 架构并不强制要求实现所有的异常等级,但 EL0,EL1 是必须要实现的。
另外,为了在 ARM64 下可以运行 ARM32 程序,所以 ARM64 CPU 有一个 AArch32 状态,上面的中断向量表 vectors 也定义了 AArch32 的中断向量表。
2.2 ARM64 中断向量表配置 和 中断处理流程
ARM64 向量的排列定义如下:

看下 ARM64 Linux 中断向量表的定义:
c
// arch/arm64/kernel/entry.S
/*
* Exception vectors.
*/
.pushsection ".entry.text", "ax"
.align 11
ENTRY(vectors)
// AArch64 态, EL1异常等级(即内核态) 的 4 种类型异常 的 中断向量入口
// 使用栈指针 SP_EL0
kernel_ventry 1, sync_invalid // Synchronous EL1t
kernel_ventry 1, irq_invalid // IRQ EL1t
kernel_ventry 1, fiq_invalid // FIQ EL1t
kernel_ventry 1, error_invalid // Error EL1t
// AArch64 态, EL1异常等级(即内核态) 的 4 种类型异常 的 中断向量入口
// 使用栈指针 SP_EL1
kernel_ventry 1, sync // Synchronous EL1h
kernel_ventry 1, irq // IRQ EL1h
kernel_ventry 1, fiq_invalid // FIQ EL1h
kernel_ventry 1, error_invalid // Error EL1h
// AArch64 态, EL0异常等级(即用户态) 的 4 种类型异常 的 中断向量入口
kernel_ventry 0, sync // Synchronous 64-bit EL0
kernel_ventry 0, irq // IRQ 64-bit EL0
kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0
kernel_ventry 0, error_invalid // Error 64-bit EL0
// AArch32 态, EL0异常等级(即用户态) 的 4 种类型异常 的 中断向量入口
#ifdef CONFIG_COMPAT
kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0
kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0
kernel_ventry 0, error_invalid_compat, 32 // Error 32-bit EL0
#else
kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0
kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0
kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
#endif
END(vectors)
每个异常属于以下 4 种类型之一:

中断向量为每个异常等级 ELx 的上图中每一种异常类型定义一个处理入口,如 vectors 表中为 EL1 的 Synchronous exception,IRQ,FIQ,SError 定义了如下 4 个异常类型处理入口:
c
// AArch64 态, EL1异常等级(即内核态) 的 4 种类型异常 的 中断向量入口
// 使用栈指针 SP_EL0
kernel_ventry 1, sync_invalid // Synchronous EL1t
kernel_ventry 1, irq_invalid // IRQ EL1t
kernel_ventry 1, fiq_invalid // FIQ EL1t
kernel_ventry 1, error_invalid // Error EL1t
接下来是设置中断向量表地址到 vbar_el1 寄存器:
c
// arch/arm64/kernel/head.S
__primary_switched: // BOOT CPU
...
// 配置 EL1 异常向量表 (虚拟地址 到 vbar_el1 寄存器)
adr_l x8, vectors // load VBAR_EL1 with virtual
msr vbar_el1, x8 // vector table address
isb
...
b start_kernel
__secondary_switched: // 非 BOOT CPU
// 配置 EL1 异常向量表 (虚拟地址 到 vbar_el1 寄存器)
adr_l x5, vectors
msr vbar_el1, x5
isb
...
b secondary_start_kernel
ENDPROC(__secondary_switched)
当发生中断时,CPU 进入对应的异常等级,然后定位到对应的异常类型入口进行处理。本文只简要的讨论下 ARM64 Linux 内核下 IRQ 异常的处理过程,对其它异常感兴趣的读者,可自行阅读相关源码。
看看 ARM64 Linux 下内核态(EL1) IRQ 异常的处理流程:
c
.align 6
el1_irq:
kernel_entry 1
enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
irq_handler // IRQ 中断处理
// 处理完 IRQ 异常后,检测是否有挂起的抢占需求,如果有则进行调度抢占
#ifdef CONFIG_PREEMPT
ldr w24, [tsk, #TSK_TI_PREEMPT] // get preempt count
cbnz w24, 1f // preempt count != 0
ldr x0, [tsk, #TSK_TI_FLAGS] // get flags
tbz x0, #TIF_NEED_RESCHED, 1f // needs rescheduling?
bl el1_preempt
1:
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on
#endif
kernel_exit 1
ENDPROC(el1_irq)
/*
* Interrupt handling.
*/
.macro irq_handler
ldr_l x1, handle_arch_irq // gic_handle_irq()
mov x0, sp
irq_stack_entry
blr x1
irq_stack_exit
.endm
假定使用的 GIC 中断处理芯片,则进入 gic_handle_irq() 处理 IRQ 中断,细节可参考 GIC 中断处理流程。
用户态(EL0) 的 IRQ 异常处理流程类似,只不过没有中断处理完成后抢占检测处理:
c
.align 6
el0_irq:
kernel_entry 0
el0_irq_naked:
enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
ct_user_exit
#ifdef CONFIG_HARDEN_BRANCH_PREDICTOR
tbz x22, #55, 1f
bl do_el0_irq_bp_hardening
1:
#endif
irq_handler
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on
#endif
b ret_to_user
ENDPROC(el0_irq)
其中的 kernel_entry,kernel_exit,ret_to_user 主要是做上下文保存和恢复操作。
2.3 其它:RESET 异常 和 系统调用
ARM64 特殊的硬件 RESET 异常是比较特殊的:

CPU 的硬件复位轮不到 Linux 来处理,因为这一般是 SoC 厂家固化 ROM 的工作,了解下即可。
最后,ARM32 使用 SWI 指令实现系统调用,在 ARM64 则切换为 SVC(Supervisor Call),HVC(Hypervisor Call)。ARM64 系统调用概要流程为:
bash
触发 sync 异常
发出 SVC 指令 ------------> 进入 sync 异常处理入口 ---> 调用系统调用
c
/*
* EL0 mode handlers.
*/
.align 6
el0_sync:
kernel_entry 0
mrs x25, esr_el1 // read the syndrome register
lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class
cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state
b.eq el0_svc // AArch64 系统调用
...
#ifdef CONFIG_COMPAT
.align 6
el0_sync_compat:
kernel_entry 0, 32
mrs x25, esr_el1 // read the syndrome register
lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class
cmp x24, #ESR_ELx_EC_SVC32 // SVC in 32-bit state
b.eq el0_svc_compat
...
el0_svc_compat:
/*
* AArch32 syscall handling
*/
adrp stbl, compat_sys_call_table // load compat syscall table pointer
mov wscno, w7 // syscall number in w7 (r7)
mov wsc_nr, #__NR_compat_syscalls
b el0_svc_naked // AArch32 系统调用
.align 6
el0_irq_compat:
kernel_entry 0, 32
b el0_irq_naked
#endif
...
b ret_to_user
ENDPROC(el0_sync)
/*
* SVC handler.
*/
.align 6
el0_svc:
adrp stbl, sys_call_table // load syscall table pointer
mov wscno, w8 // syscall number in w8
mov wsc_nr, #__NR_syscalls
el0_svc_naked: // compat entry point
stp x0, xscno, [sp, #S_ORIG_X0] // save the original x0 and syscall number
enable_dbg_and_irq
ct_user_exit 1
ldr x16, [tsk, #TSK_TI_FLAGS] // check for syscall hooks
tst x16, #_TIF_SYSCALL_WORK
b.ne __sys_trace
cmp wscno, wsc_nr // check upper syscall limit
b.hs ni_sys
mask_nospec64 xscno, xsc_nr, x19 // enforce bounds for syscall number
ldr x16, [stbl, xscno, lsl #3] // address in the syscall table
// 调用系统调用 #nr
blr x16 // call sys_* routine
b ret_fast_syscall
ni_sys:
mov x0, sp
bl do_ni_syscall
b ret_fast_syscall
ENDPROC(el0_svc)