Linux:ARM64 中断处理简析

文章目录

  • [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)
相关推荐
用户9718356334662 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 小时前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠19 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush419 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行52020 小时前
Linux 11 动态监控指令top
linux
不会C语言的男孩21 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
古城小栈21 小时前
Unix 与 Linux 异同小叙
linux·服务器·unix
凡人叶枫1 天前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++
2601_961875241 天前
决战申论100题2026|最新|范文
linux·容器·centos·debian·ssh·fabric·vagrant
java_cj1 天前
深入kube-apiserver认证机制:从Bearer Token到mTLS的完整认证链解析
linux·运维·服务器·云原生·容器·kubernetes