ARM64 平台 Linux-6.18.38 版本
ARM64 架构采用四级异常级别(Exception Level, EL)权限金字塔模型,这四个级别从 EL0 到 EL3,随着数字的增加,软件的执行权限也相应增加
在未启用 KVM 等虚拟化技术的情况下,Linux 操作系统的内核运行于 EL1,而用户空间的应用程序运行于 EL0
User | 权限最低 | |
Kernel | 特权模式 | |
Hypervisor | 较高权限 | |
Secure Monitor | 权限最高 |
ARM64 和异常处理相关的硬件寄存器
内核:入口读出保存,返回前恢复 | ||
内核:返回时恢复(由 ERET 生效) | ||
硬件:异常发生时读取并跳转 | ||
内核:通过系统寄存器间接控制 | ||
内核:读取以检测和处理异常 | ||
当发生 IRQ 时,CPU 会根据中断发生时的当前异常级别以及使用的栈指针,跳转到 EL1 异常向量表中对应的入口进行处理
kernel_ventry 1, t, 64, irq | ||
kernel_ventry 1, h, 64, irq | ||
kernel_ventry 0, t, 64, irq | ||
kernel_ventry 0, t, 32, irq |
ARM64 异常向量表代码是由 kernel_ventry 宏定义的
核心职责可以精简为以下三点:
overflow_stack),防止内核崩溃。el1h_64_irq)。下面时宏展开代码的详细注释
/*
* https://elixir.bootlin.com/linux/v6.18.38/source/arch/arm64/kernel/entry.S
* 定义宏 kernel_ventry,接收 4 个必填参数 (req = required):
* el : 异常级别 (0 = EL0 用户态, 1 = EL1 内核态)
* ht : 栈指针模式 (t = Task/Thread 模式使用 SP_EL0, h = Handler 模式使用 SP_EL1)
* regsize: 寄存器位宽 (32 或 64)
* label : 异常类型标签 (如 sync, irq, fiq, error)
*/
.macro kernel_ventry, el:req, ht:req, regsize:req, label:req
.align 7 // 强制 128 字节对齐 (2^7 = 128)
// ARM64 硬件规范要求每个异常向量入口必须严格占据 128 字节
.Lventry_start\@: // 定义局部标签记录当前入口起始地址
.if \el == 0 // 仅当异常来自用户态 EL0 时,才执行以下代码块
/*
* ========== 异常来自用户态 EL0 ==========
* 这条分支指令必须是 EL0 向量表入口的第一条指令。
* 在 KPTI (内核页表隔离) 机制下,Trampoline (蹦床) 向量表会故意跳过这条指令,
* 从而触发下面的清理操作。
*/
b .Lskip_tramp_vectors_cleanup\@ // 正常进入向量表时,直接跳过下面的清理代码
.if \regsize == 64 // 如果是 64 位用户态
mrs x30, tpidrro_el0 // 从只读线程 ID 寄存器读取值到 x30 (用于 KPTI 恢复)
msr tpidrro_el0, xzr // 将 tpidrro_el0 清零,防止用户态遗留的恶意数据干扰内核
.else // 如果是 32 位兼容模式
mov x30, xzr // 直接将 x30 清零
.endif
.Lskip_tramp_vectors_cleanup\@: // 正常流程或 Trampoline 清理完毕后的继续执行点
.endif // 结束 EL0 条件编译
/*
* ========== 核心精华:无损栈溢出检测机制 ==========
* 此时通用寄存器尚未保存到栈中,因此绝对不能破坏任何 GPR 来检测栈是否溢出。
* 内核利用加减法数学运算,巧妙地在不破坏 sp 和 x0 的前提下完成了检测。
*/
sub sp, sp, #PT_REGS_SIZE // 在内核栈上预留出 pt_regs 结构体大小的空间
// sp' = sp - sizoef(struct pt_regs)
add sp, sp, x0 // 步骤1: sp'' = sp' + x0
sub x0, sp, x0 // 步骤2: x0' = sp'' - x0 = (sp' + x0) - x0 = sp'
// x0' 现在保存了 sp'
tbnz x0, #THREAD_SHIFT, 0f // 步骤3: 测试恢复出的原 sp' 的第 THREAD_SHIFT 位
// 内核栈是 THREAD_SIZE 对齐的,未溢出时该位必为 0
// 如果不为 0 (tbnz 触发),说明栈溢出了,跳转到标签 '0' 进行紧急处理
sub x0, sp, x0 // 步骤4: x0'' = sp'' - x0' = (sp' + x0) - sp' = x0
// x0'' 恢复了原来的 x0
sub sp, sp, x0 // 步骤5: sp''' = sp'' - x0'' = (sp' + x0) - x0 = sp'
// sp'' 恢复了原来的值 sp'
b el\el\ht\()_\regsize\()_\label // 栈未溢出,直接跳转到具体的 C 语言处理函数
0: // 【紧急逃生通道】如果上面的 tbnz 发现栈溢出,跳转到这里
/*
* 既然栈已经溢出,或者已经在溢出栈上再次发生异常,
* 我们就不打算返回用户态了,因此可以随意破坏 (clobber) EL0 寄存器来腾出通用寄存器使用。
*/
/* 将当前的 sp (即原 sp 减去 PT_REGS_SIZE 的值) 暂存到系统寄存器 tpidr_el0 中 */
// sp'' = sp' + x0
// x0' = sp'
msr tpidr_el0, x0 // tpidr_el0 = x0'
/* 通过数学运算恢复 x0 的原始值,并暂存到 tpidrro_el0 中 */
sub x0, sp, x0 // x0'' = sp'' - x0' = (sp' + x0) - sp' = x0
msr tpidrro_el0, x0 // tpidrro_el0 = x0'' = x0
/* 强制将栈指针切换到当前 CPU 专属的安全溢出栈 (overflow stack) 的顶部 */
adr_this_cpu sp, overflow_stack + OVERFLOW_STACK_SIZE, x0 // sp 更新,SP
/*
* 检查我们是否已经处于溢出栈上。
* 这种情况可能发生在内核 panic() 后重新开启中断的极端恶劣情况下。
*/
mrs x0, tpidr_el0 // X0 = sp'
sub x0, sp, x0 // X0' = SP - X0 = SP - sp'
tst x0, #~(OVERFLOW_STACK_SIZE - 1) // 测试差值是否在溢出栈的合法范围内
b.ne __bad_stack // 如果不在范围内 (说明溢出栈也溢出了),跳转到 __bad_stack 报错死机
/* 如果差值合法,说明这是第一次栈溢出,恢复 sp/x0 并继续执行 */
sub sp, sp, x0 // SP' = SP - X0' = SP - (SP - sp') = sp'
mrs x0, tpidrro_el0 // X0 = x0
// 此时 SP 和 X0 都已经恢复
b el\el\ht\()_\regsize\()_\label // 跳转到具体的异常处理函数
.org .Lventry_start\@ + 128 // 【防御性校验】告诉汇编器:当前宏生成的代码必须精确填充到起始地址往后 128 字节
// 如果代码超过 128 字节,汇编器会直接报错,确保严格符合硬件规范
.endm // 宏定义结束b el\el\ht\()_\regsize\()_\label 会跳转具体的异常处理 C 语言函数
.macro entry_handler el:req, ht:req, regsize:req, label:req
SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label) // 生成汇编函数入口(如 el1h_64_irq)
kernel_entry \el, \regsize // 保存所有寄存器到现场(构建 pt_regs)
mov x0, sp // 将栈指针作为第一个参数传给 C 函数
bl el\el\ht\()_\regsize\()_\label\()_handler // 调用对应的 C 语言处理函数(如 el1h_64_irq_handler)
.if \el == 0
b ret_to_user // 若来自用户态(EL0),返回用户态
.else
b ret_to_kernel // 若来自内核态(EL1),返回内核态
.endif
SYM_CODE_END(el\el\ht\()_\regsize\()_\label) // 标记函数结束
.endm那么 IRQ 处理的函数如下
kernel_ventry 1, t, 64, irq | |
kernel_ventry 1, h, 64, irq | |
kernel_ventry 0, t, 64, irq | |
kernel_ventry 0, t, 32, irq |
最后统一使用 el0_interrupt 和 el1_interrupt() 两个函数处理
asmlinkage void noinstr el1h_64_irq_handler(struct pt_regs *regs)
{
el1_interrupt(regs, handle_arch_irq);
}
asmlinkage void noinstr el0t_64_irq_handler(struct pt_regs *regs)
{
__el0_irq_handler_common(regs);
}
asmlinkage void noinstr el0t_32_irq_handler(struct pt_regs *regs)
{
__el0_irq_handler_common(regs);
}
static void noinstr __el0_irq_handler_common(struct pt_regs *regs)
{
el0_interrupt(regs, handle_arch_irq);
}最后统一使用 do_interrupt_handler() 处理 EL0 和 EL1 的中断
static __always_inline void __el1_irq(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
irqentry_state_t state;
state = enter_from_kernel_mode(regs);
irq_enter_rcu();
do_interrupt_handler(regs, handler);
irq_exit_rcu();
exit_to_kernel_mode(regs, state);
}
static void noinstr el1_interrupt(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
write_sysreg(DAIF_PROCCTX_NOIRQ, daif);
if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && regs_irqs_disabled(regs))
__el1_pnmi(regs, handler);
else
__el1_irq(regs, handler);
}
static void noinstr el0_interrupt(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
arm64_enter_from_user_mode(regs);
write_sysreg(DAIF_PROCCTX_NOIRQ, daif);
if (regs->pc & BIT(55))
arm64_apply_bp_hardening();
irq_enter_rcu();
do_interrupt_handler(regs, handler);
irq_exit_rcu();
arm64_exit_to_user_mode(regs);
}do_interrupt_handler() 函数调用传入的 handler - handle_arch_irq
static void do_interrupt_handler(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
struct pt_regs *old_regs = set_irq_regs(regs);
if (on_thread_stack())
call_on_irq_stack(regs, handler);
else
handler(regs);
set_irq_regs(old_regs);
}handle_arch_irq 是一个函数指针,set_handle_irq() 函数可以注册 IRQ 处理的 handler
void (*handle_arch_irq)(struct pt_regs *) __ro_after_init = default_handle_irq;
void (*handle_arch_fiq)(struct pt_regs *) __ro_after_init = default_handle_fiq;
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq != default_handle_irq)
return -EBUSY;
handle_arch_irq = handle_irq;
pr_info("Root IRQ handler: %ps\n", handle_irq);
return 0;
}ARM64 最标准、最核心的 IRQ 驱动是 GIC 驱动,但具体的驱动选择是由底层硬件设备树决定的
以 GIC 驱动为例,分析 IRQ 派发流程
GIC 驱动初始化时,调用 set_handle_irq() 函数注册 gic_handle_irq
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
if (unlikely(gic_supports_nmi() && !interrupts_enabled(regs)))
__gic_handle_irq_from_irqsoff(regs);
else
__gic_handle_irq_from_irqson(regs); // 通常的分支
}__gic_handle_irq_from_irqson() 函数的逻辑
static void __gic_handle_irq_from_irqson(struct pt_regs *regs)
{
bool is_nmi;
u32 irqnr;
irqnr = gic_read_iar(); // 获得当前待处理的中断号
// 将中断从 Pending 推进到 Active 状态
is_nmi = gic_rpr_is_nmi_prio();
if (is_nmi) {
nmi_enter();
__gic_handle_nmi(irqnr, regs);
nmi_exit();
}
if (gic_prio_masking_enabled()) { // 启用了 GIC 优先级屏蔽(priority masking)特性
gic_pmr_mask_irqs(); // 修改 GIC 的 PMR 寄存器
// 允许比当前中断优先级更高的中断打断自己
gic_arch_enable_irqs(); // 重新打开中断总开关
}
if (!is_nmi)
__gic_handle_irq(irqnr, regs); // 处理该中断
}__gic_handle_irq() 函数继续把从 GIC 获取的硬件中断号交给内核的 IRQ 子系统处理
static void __gic_handle_irq(u32 irqnr, struct pt_regs *regs)
{
if (gic_irqnr_is_special(irqnr)) // 过滤特殊中断号
return;
gic_complete_ack(irqnr); // 完成硬件 ACK,通知 GIC 可以更新内部状态
// 允许更高优先级的中断来抢占我
if (generic_handle_domain_irq(gic_data.domain, irqnr)) {
WARN_ONCE(true, "Unexpected interrupt (irqnr %u)\n", irqnr);
gic_deactivate_unhandled(irqnr);
}
}generic_handle_domain_irq() 函数主要职责:
int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq)
{
return handle_irq_desc(irq_resolve_mapping(domain, hwirq));
}handle_irq_desc() 函数最后调用 IRQ 上半部处理函数
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
desc->handle_irq(desc);
}
int handle_irq_desc(struct irq_desc *desc)
{
struct irq_data *data;
if (!desc)
return -EINVAL;
data = irq_desc_get_irq_data(desc);
if (WARN_ON_ONCE(!in_hardirq() && irqd_is_handle_enforce_irqctx(data)))
return -EPERM;
generic_handle_irq_desc(desc); // 触发设备驱动的上半部
return 0;
}物理 IRQ(hwirq)与 Linux 虚拟 IRQ(virq)之间的映射,是通过 irq_domain(中断域)机制建立的
irq_desc {} 对象表示一个虚拟 IRQ,所有已经分配的虚拟 IRQ 保存在 space_irqs 对象中
irq_domain 是对底层中断控制器的软件抽象:revmap 和 revmap_tree 中保存了 hwirq ==> irq 的映射
通过 revmap 和 revmap_tree 可以找到 hwirq 对应的虚拟 IRQ

在现代 SoC 中,irq_domain 不是孤立的,而是呈树状层级结构,例如,GPIO 控制器的 irq_domain 会通过 parent 指针挂载到 GIC 的 irq_domain 下
gpio5: gpio@20a8000 {
compatible = "fsl,imx6ul-gpio";
reg = <0x020a8000 0x4000>;
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>; // 中断输出线,物理连接到了 GIC 的第 74 号 SPI 中断线上
interrupt-parent = <&gic>; // 明确声明父级是 GIC
gpio-controller;
#gpio-cells = <2>;
interrupt-controller; // 声明自己也是一个中断控制器
#interrupt-cells = <2>;
};__irq_resolve_mapping() 函数用于找到物理 hwirq 对应的虚拟 IRQ 及 irq_desc 对象
struct irq_desc *__irq_resolve_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq,
unsigned int *irq)
{
struct irq_desc *desc = NULL;
struct irq_data *data;
/* Look for default domain if necessary */
if (domain == NULL)
domain = irq_default_domain;
if (domain == NULL)
return desc;
if (irq_domain_is_nomap(domain)) { // hwirq == virq
if (hwirq < domain->hwirq_max) {
data = irq_domain_get_irq_data(domain, hwirq);
if (data && data->hwirq == hwirq)
desc = irq_data_to_desc(data);
if (irq && desc)
*irq = hwirq;
}
return desc;
}
rcu_read_lock();
/* Check if the hwirq is in the linear revmap. */
if (hwirq < domain->revmap_size)
data = rcu_dereference(domain->revmap[hwirq]);
else
data = radix_tree_lookup(&domain->revmap_tree, hwirq);
if (likely(data)) {
desc = irq_data_to_desc(data);
if (irq)
*irq = data->irq;
}
rcu_read_unlock();
return desc;
}在 Linux 内核驱动开发中,中断处理的核心原则是快进快出
由于硬件中断发生时 CPU 处于中断上下文,此时系统关闭了抢占甚至屏蔽了同级中断,任何耗时操作都会导致系统响应延迟甚至死锁
为了解决这一矛盾,Linux 将中断处理逻辑拆分为两个阶段
把耗时的、可延后或可被并行处理的工作从上半部移走,减少中断关闭时间,提升系统响应
随着内核版本的演进,下半部的实现机制已从传统的 Softirq/Tasklet 逐渐向 Workqueue 和 Threaded IRQ 迁移,理解它们的执行上下文差异是编写高质量驱动的关键
为了彻底解决复杂中断处理带来的实时性问题,Linux 引入了 Threaded IRQ(线程化中断) 机制,通过直接调用 request_threaded_irq(),开发者可以将中断处理完美地一分为二
request_irq() 函数也是 request_threaded_irq() 函数的封装
extern int __must_check
request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags, const char *name, void *dev);
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags | IRQF_COND_ONESHOT, name, dev);
}设置的 handler 和 thread_fn 封装成 irqaction {} 对象,添加到 irq_desc {} 对象中

handle_irq_event_percpu() 函数会遍历 irq_desc 对象上 action 链表上的所有 irqaction 对象,执行 handler 和 thread_fn
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
irqreturn_t retval;
retval = __handle_irq_event_percpu(desc);
add_interrupt_randomness(desc->irq_data.irq);
if (!irq_settings_no_debug(desc))
note_interrupt(desc, retval);
return retval;
}
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;
record_irq_time(desc);
for_each_action_of_desc(desc, action) { // 遍历该中断描述符下的所有 irqaction 链表
irqreturn_t res;
/*
* If this IRQ would be threaded under force_irqthreads, mark it so.
*/
if (irq_settings_can_thread(desc) &&
!(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
lockdep_hardirq_threaded();
trace_irq_handler_entry(irq, action);
if (static_branch_unlikely(&irqhandler_duration_check_enabled)) {
u64 ts_start = local_clock();
res = action->handler(irq, action->dev_id);
irqhandler_duration_check(ts_start, irq, action);
} else {
res = action->handler(irq, action->dev_id); // 处理上半部
}
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
irq, action->handler))
local_irq_disable();
switch (res) {
case IRQ_WAKE_THREAD:
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
__irq_wake_thread(desc, action); // 通知线程处理下半部
break;
default:
break;
}
retval |= res;
}
return retval;
}devm_request_threaded_irq() 函数在 request_threaded_irq 的基础上,引入了内核的 设备资源管理机制,是更推荐的 API
free_irq,无需开发者手动清理extern int __must_check
devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname,
void *dev_id);
static inline int __must_check
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags | IRQF_COND_ONESHOT,
devname, dev_id);
}