中断执行流程
本文基于 x86_64 构架分析 Linux5.18.2代码。
总结了硬软中断的执行流程。
1 硬中断
通过 DEFINE_IDTENTRY_IRQ(common_interrupt)
函数定义了通用的中断处理程序。
DEFINE_IDTENTRY_IRQ(common_interrupt)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc *desc;
RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");
desc = __this_cpu_read(vector_irq[vector]);
if (likely(!IS_ERR_OR_NULL(desc))){
handle_irq(desc, regs);
} else {
ack_APIC_irq();
if (desc == VECTOR_UNUSED) {
pr_emerg_ratelimited("%s: %d.%u No irq handler for vector\n", __func__, smp_processor_id(), vector);
} else {
__this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
}
}
set_irq_regs(old_regs);
}
irq_enter/exit_rcu() 在函数体之前调用,并且设置 KVM L1D 刷新请求。
如果需要,堆栈切换到中断堆栈必须在函数体内完成。
该函数采用 DEFINE_IDTENTRY_IRQ(common_interrupt)
宏来定义。具体的宏实现为:
#define DEFINE_IDTENTRY_IRQ(func) \
static void __##func(struct pt_regs *regs, u32 vector); \
\
__visible noinstr void func(struct pt_regs *regs, \
unsigned long error_code) \
{ \
irqentry_state_t state = irqentry_enter(regs); \
u32 vector = (u32)(u8)error_code; \
\
instrumentation_begin(); \
kvm_set_cpu_l1tf_flush_l1d(); \
run_irq_on_irqstack_cond(__##func, regs, vector); \
instrumentation_end(); \
irqentry_exit(regs, state); \
} \
\
static noinline void __##func(struct pt_regs *regs, u32 vector)
DEFINE_IDTENTRY_IRQ()
宏定义了发出设备中断 IDT
入口点的代码。其中 @func
为入口点的函数名称,中断向量号由低级入口存根推送,并作为 error_code
参数传递给函数。
该宏中有三个重要的部分:
irqentry_enter()
run_irq_on_irqstack_cond()
irqentry_exit()
irqentry_enter()
/irqentry_exit()
函数会在真正的中断处理程序执行前/后进行一些准备/善后工作,例如是否是从用户空间产生中断,是否需要更新 RCU ,在中断退出时检查是否需要重新调度等。
run_irq_on_irqstack_cond()
宏负责将真正的中断处理程序放到中断栈上运行,并在中断处理程序结束时调用软中断响应的函数来处理挂起的软中断。
其中重要的宏为 run_irq_on_irqstack_cond(__##func, regs, vector)
#define run_irq_on_irqstack_cond(func, regs, vector) \
{ \
assert_function_type(func, void (*)(struct pt_regs *, u32)); \
assert_arg_type(regs, struct pt_regs *); \
assert_arg_type(vector, u32); \
\
call_on_irqstack_cond(func, regs, ASM_CALL_IRQ, \
IRQ_CONSTRAINTS, regs, vector); \
}
该宏通过call_on_irqstack_cond()
宏在中断栈上执行__##func
函数。
硬中断调用流程图
通用硬中断处理函数流程图
2 软中断
软中断并不是真正的中断,而是由硬中断在通用处理函数在退出中断前调用的子函数。由于其在中断的上下文中执行,所以称之为软中断。
软中断可以分为在中断上下文执行的软中断和在软中断线程执行的软中断。当一次年软中断的执行超过约束的时候就会从中断上下文切换到软中断线程上下文中运行(软中断线程运行在 CFS 调度类下,优先级为 20,调度策略为 SCHED_NORMAL),这样做的目的是防止软中断长时间占用 CPU 的资源,从而使其他进程/线程处于饥饿状态。
软中断约束:
- 一次软中断持续执行时间不能超过 2 ms;
- 没有需要重新调度的进程;
- 一次软中断中的循环次数不能超过 10 次;
软中断处理流程图