当 CPU 发生同步异常、IRQ、FIQ 或 SError 时,硬件根据当前异常级别(EL)和异常类型,跳转到 VBAR_ELx 指向的向量表条目。Linux 内核在 entry.S 中构造这张表,并在 setup_arch 早期通过 cpu_setup() 把物理地址写入 VBAR_EL1。理解 vectors 的分区与 kernel_ventry 宏,是分析缺页、系统调用、中断入口的第一把钥匙------本文聚焦向量表本身,不重复 EL 权限模型与 head.S 早期启动流程。
源码锚点
| 路径 | 作用 |
|---|---|
arch/arm64/kernel/entry.S |
vectors 表、kernel_ventry、kernel_entry |
arch/arm64/kernel/entry-common.c |
C 侧异常处理、do_el0_svc 等 |
arch/arm64/kernel/traps.c |
非法指令、undef、panic 路径 |
arch/arm64/kernel/irq.c |
handle_arch_irq 函数指针 |
arch/arm64/kernel/setup.c |
cpu_setup() 设置 VBAR |
arch/arm64/include/asm/exception.h |
struct pt_regs、异常帧布局 |
向量表主体(每个 slot 128 字节对齐):
asm
/* arch/arm64/kernel/entry.S */
.align 11
ENTRY(vectors)
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
kernel_ventry 1, sync // Synchronous EL1h
kernel_ventry 1, irq // IRQ EL1h
kernel_ventry 1, fiq // FIQ EL1h
kernel_ventry 1, error // Error EL1h
kernel_ventry 0, sync // Synchronous 64-bit EL0
kernel_ventry 0, irq // IRQ 64-bit EL0
kernel_ventry 0, fiq // FIQ 64-bit EL0
kernel_ventry 0, error // Error 64-bit EL0
kernel_ventry 在栈上分配 struct pt_regs 并保存通用寄存器:
asm
.macro kernel_ventry, el, label, regsize = 64
.align 7
.Lventry_start\@:
sub sp, sp, #S_FRAME_SIZE
/* 保存 x0-x29、lr、elr_el1、spsr_el1 等到 pt_regs */
b \label
.endm
VBAR 安装(每 CPU 在 bring-up 时执行):
c
/* arch/arm64/kernel/setup.c --- cpu_setup 概念路径 */
void __init cpu_setup(char *fdt)
{
/* ... */
write_sysreg((u64)vectors, vbar_el1);
isb();
}
EL0 系统调用从 sync 向量进入 el0_svc_common:
asm
/* entry.S --- el0_svc 附近 */
el0_svc:
adrp x16, vectors
b el0_svc_common
调用链
text
硬件异常(例:EL0 发 svc #0)
→ PC 跳至 VBAR_EL1 + offset(EL0 sync slot)
→ kernel_ventry 0, sync
→ 构造 pt_regs,b el0_sync
→ el0_svc / el0_da / el0_ia / el0_ti ...
→ do_el0_svc(regs) /* 系统调用 */
→ do_mem_abort() /* 缺页/权限 fault */
→ ret_to_user → eret
硬件 IRQ(EL0/EL1)
→ vectors → kernel_ventry → el0_irq / el1_irq
→ enter_el1_irq_or_nmi
→ irq_handler(regs)
→ handle_arch_irq(regs) /* 由 GIC driver 注册 */
→ generic_handle_domain_irq()
→ irq_exit()
→ ret_to_user / ret_to_kernel
VBAR 变更场景:
text
cpu_startup_entry() / secondary_start_kernel()
→ cpu_setup() 或 smp 路径再次确认 vbar_el1
KVM/VHE:host 可能在 EL2 使用 vbar_el2(与本篇 EL1 Linux 主路径分离)
重点知识
16 个向量 slot: AArch64 规定每个异常类型 ×(当前 EL + SP 选择)共 16 组入口;Linux 主要使用 EL1h(内核用 SP_EL1)与 EL0(用户态)四组;EL1t 多为 *_invalid 陷阱误用 SP_EL0 的内核代码。
128 字节对齐: 每条 kernel_ventry 占 128 字节(.align 7),便于 VBAR 基址低 11 位为零;修改向量表需保证链接脚本不把 vectors 拆散。
pt_regs 与 eret: 异常返回依赖 elr_el1(返回 PC)与 spsr_el1(返回 PSTATE);内核 patch 这些字段可实现信号 delivery、ptrace 单步。
IRQ 与 preempt: el0_irq 路径在 irq_exit() 中检查 TIF_NEED_RESCHED;理解向量入口栈(sync 用 task 内核栈 vs IRQ stack)对 lockdep 与 stack overflow 排障至关重要。
SError: error 向量处理异步外部 abort(如 AXI 错误);未注册 handler 时可能 panic;设备驱动 DMA 错误有时经此上报。
与 VBAR_EL3 区别: TF-A BL31 在 EL3 有自己的 vbar_el3 处理 SMC;Linux 运行在 EL1,仅写 vbar_el1。EL2 存在时(KVM nVHE)另有独立向量表。
调试: crash 中 dis vectors;ftrace function_graph 从 el0_svc 跟踪;/proc/kallsyms 查 vectors 符号地址应与 VBAR_EL1 一致。
Checklist
-
VBAR_EL1指向vectors符号,16 组入口地址随内核版本与配置(KASLR)变化 - 用户 syscall、缺页、IRQ 分别对应 EL0 sync/irq 路径,而非 EL1t invalid
- 修改
entry.S后检查 128 字节对齐与S_FRAME_SIZE栈布局 - IRQ 处理经
handle_arch_irq,与 GICv3 驱动注册一致 - SError 路径有平台
arm64_serror_panic或自定义 handler - secondary CPU 启动后 VBAR 与 primary 一致,无旧 Bootloader 残留