Linux 内核中断与中断处理(Interrupts and Interrupt Handling)全解析:从 IDT 到软中断 文档教程操作系统【免费下载链接】linux-insidesA book-in-progress about the Linux kernel and its insides.项目地址https://gitcode.com/gh_mirrors/li/linux-insides点击查看免费下载本指南基于 linux-insides 仓库的 Interrupts 章节 及其十个系列文档系统梳理 Linux 内核x86_64中断与异常处理的完整脉络从硬件中断理论、中断描述符表IDT与中断栈的初始化到各类异常处理器、外部硬件中断、NMI以及 softirq / tasklet / workqueue 延迟中断机制。读完本篇你将掌握中断向量号与 IDT 门结构、IST 栈切换机制、异常处理器的汇编入口与 C 实现之间的调用关系并理解一个真实设备驱动串口驱动如何注册与处理中断。章节总览Interrupts 系列十讲Interrupts/README.md 是《linux-insides》书中「Interrupts and Interrupt Handling」一章的目录索引覆盖中断和异常处理的完整知识链按十个部分循序渐进部分主题仓库文档Part 1中断与中断处理理论IDT、向量号、门结构、IST、中断栈linux-interrupts-1.mdPart 2早期阶段的中断与异常处理初始化setup_idt、gs 寄存器、canary、local_irq_disable、early_trap_initlinux-interrupts-2.mdPart 3早期异常处理器#DB / #BP 与 idtentry 宏linux-interrupts-3.mdPart 4第一个非早期中断处理器page fault、trap_init、TSS/IST 初始化linux-interrupts-4.mdPart 5异常处理器的实现DO_ERROR 宏、do_error_trap、die 与信号发送linux-interrupts-5.mdPart 6非掩蔽中断NMI处理与架构相关剩余处理器linux-interrupts-6.mdPart 7外部硬件中断的早期初始化linux-interrupts-7.mdPart 8IRQ 的非早期初始化linux-interrupts-8.mdPart 9延迟中断Softirq、Tasklets 与 Workqueueslinux-interrupts-9.mdPart 10收尾真实硬件驱动21285 串口与中断相关实践linux-interrupts-10.md该章节的逻辑是先建立理论框架Part 1再沿着内核启动路径从 arch/x86/boot/pm.c 的 setup_idt、start_kernel 一路到 setup_arch / trap_init / init_IRQ逐层深入实现最后以真实驱动收尾。这也是本指南的展开顺序。Part 1中断理论——事件、向量号与 IDT什么是中断内核如何处理中断中断interrupt是由软件或硬件在需要获得 CPU 关注时触发的事件。以键盘按键为例按键产生中断后操作系统必须立即处理。一个完整的中断处理过程包含三个基本步骤内核暂停当前进程的执行抢占当前任务内核查找该中断的处理器并转移控制权执行中断处理器中断处理器执行完毕后被中断的进程恢复执行。当然实际过程远比这复杂但这三步构成了中断处理的基本骨架。中断控制器从 PIC 到 APIC早期机器使用可编程中断控制器PIC芯片负责按顺序处理来自多个设备的中断请求现代机器则使用高级可编程中断控制器APIC它由两个独立设备组成Local APIC位于每个 CPU 核心上负责 CPU 特定的中断配置通常用于管理 APIC 定时器、温度传感器等本地 I/O 设备产生的中断I/O APIC提供多处理器中断管理负责把外部中断分发到各个 CPU 核心。向量号与中断描述符表IDT每个中断处理器的地址都保存在一个特殊位置——中断描述符表Interrupt Descriptor TableIDT。处理器用一个唯一编号来识别中断或异常的类型这个编号称为向量号vector number它就是 IDT 的索引。向量号范围是 0 到 255内核源码中对此有明确的范围检查BUG_ON((unsigned)n 0xFF);该检查出现在中断设置相关代码中例如 set_intr_gate 与set_intr_gate_ist都会先做此断言。其中向量号 031 由处理器保留用于架构定义的异常和中断处理向量号 32255 是用户自定义中断一般分配给外部 I/O 设备使其能向处理器发送中断。中断的两大分类广义上中断可分为两大类外部硬件产生中断通过 Local APIC 或连接到 Local APIC 的处理器引脚接收软件产生的中断由处理器自身的异常条件引起有时使用特殊的架构指令典型例子是「除零」异常以及通过syscall指令退出程序。异常的三分类Fault、Trap、Abort异常与程序执行是同步的可细分为三类Fault故障在「有问题」的指令执行之前报告该指令随后可被修正修正后允许被中断的程序恢复执行Trap陷阱在 trap 指令执行之后立即报告与 fault 一样允许被中断的程序继续执行Abort中止不一定报告导致异常的精确指令且不允许被中断的程序恢复执行。可掩蔽中断与非掩蔽中断结合此前 Booting 章节 的内容中断还可分为可掩蔽maskable与非掩蔽non-maskable两类。x86_64 上可掩蔽中断可通过两条指令屏蔽sti设置 IF 标志位和cli清除 IF 标志位。内核中的实现如下static inline void native_irq_disable(void) { asm volatile(cli: : :memory); } static inline void native_irq_enable(void) { asm volatile(sti: : :memory); }非掩蔽中断则总是被报告通常硬件故障会被映射为非掩蔽中断。中断优先级表当多个异常或中断同时发生时处理器按预定义优先级依次处理。从高到低的部分优先级顺序如下优先级 1 硬件复位与机器检查RESET、Machine Check 优先级 2 任务切换陷阱TSS 中 T 标志被置位 优先级 3 外部硬件干预FLUSH、STOPCLK、SMI、INIT 优先级 4 上一条指令的陷阱断点、调试陷阱异常 优先级 5 非掩蔽中断NMI 优先级 6 可掩蔽硬件中断 优先级 7 代码断点故障 优先级 8 取下一指令时的故障代码段限长违例、代码页故障 优先级 9 解码下一指令时的故障指令长度 15 字节、无效操作码、协处理器不可用 优先级 10 执行指令时的故障溢出、BOUND 错误、无效 TSS、段不存在、栈故障、 通用保护、数据页故障、对齐检查、x87 FPU 浮点异常、SIMD 浮点异常、虚拟化异常IDT 的结构门Gate与 16 字节表项IDT 与启动阶段见过的 GDT 结构相似但条目称为**门gate**而非描述符且 GDT 首元素必须是 NULL 描述符而 IDT 并无此强制要求。x86 架构支持中断门、任务门、陷阱门三种x86_64 长模式下只能引用中断门和陷阱门。IDT 在 x86 上是 8 字节门数组在 x86_64 上是 16 字节门数组。引导早期进入保护模式前arch/x86/boot/pm.c 的setup_idt加载的是一个全 NULL 的 IDT此时太早无法处理任何中断static void setup_idt(void) { static const struct gdt_ptr null_idt {0, 0}; asm volatile(lidtl %0 : : m (null_idt)); }IDT 可位于线性地址空间任意位置但基址必须在 x86 上按 8 字节对齐、x86_64 上按 16 字节对齐。基址保存在IDTR寄存器中用LIDT加载 IDTR和SIDT读取 IDTR两条指令修改。null_idt使用的gdt_ptr结构为 48 位u16 lenu32 ptr与 IDTR 的「Limit低 16 位 Base address高 32 位」布局一致。x86_64 的 IDT 条目gate为 16 字节结构如下内核中以gate_desc数组idt_table[]表示0–15 位处理器用作处理器入口基地址的段选择符偏移16–31 位包含中断处理器入口点的段选择符基址ISTx86_64 新增的 Interrupt Stack Table 机制DPL描述符特权级P段存在标志48–63 位处理器基址的第二部分64–95 位处理器基址的第三部分96–127 位CPU 保留。对应的内核结构gate_struct见 linux-interrupts-1.md 引用的 desc_defs.hstruct gate_struct { u16 offset_low; u16 segment; struct idt_bits bits; u16 offset_middle; #ifdef CONFIG_X86_64 u32 offset_high; u32 reserved; #endif } __attribute__((packed));ISTx86_64 的栈切换机制ISTInterrupt Stack Table是 x86_64 的新机制替代传统栈切换模式启用后它会无条件切换栈并且可以针对 IDT 中某个特定中断开启。每个 CPU 的 TSS任务状态段最多提供 7 个 IST 指针每个指针由 IDT 中的中断门引用。部分 IST 条目#define DOUBLEFAULT_STACK 1 #define NMI_STACK 2 #define DEBUG_STACK 3 #define MCE_STACK 4当异常发生时中断栈帧在 64 位模式下固定为 8 字节压栈形成如下布局含错误码--------------- | SS | 40 | RSP | 32 | RFLAGS | 24 | CS | 16 | RIP | 8 | Error code | 0 ---------------若中断门的 IST 字段非 0则把 IST 指针读入rsp有错误码的向量压入错误码否则压入伪错误码以保持栈一致性随后从门描述符加载段选择符到 CS并校验目标代码段是 64 位模式代码段即 GDT 中的 L 位最后把门描述符的偏移字段加载到rip作为处理器入口。处理器执行完iret指令返回被中断进程iret会无条件弹出ss:rsp恢复栈。中断栈与 per-cpu 数据结构x86_64 上每个线程都有大内核栈大小由THREAD_SIZE决定#define THREAD_SIZE_ORDER (2 KASAN_STACK_ORDER) #define THREAD_SIZE (PAGE_SIZE THREAD_SIZE_ORDER)CONFIG_KASAN开启时KASAN_STACK_ORDER为 1否则为 0因此THREAD_SIZE在未开启 KASAN 时为 16384 字节开启时为 32768 字节。除线程栈外每个 CPU 还有若干专用 per-cpu 栈其中处理外部硬件中断的interrupt stack大小为#define IRQ_STACK_ORDER (2 KASAN_STACK_ORDER) #define IRQ_STACK_SIZE (PAGE_SIZE IRQ_STACK_ORDER)即 16384 字节由irq_stack结构表示内含char stack[IRQ_STACK_SIZE]按IRQ_STACK_SIZE对齐。fixed_percpu_data是 per-cpu 区第一个数据对象包含gs_base[40]与stack_canary两个字段GCC 要求栈 canary 位于 gs 基址固定偏移处x86_64 为 40x86 为 20gs寄存器始终指向fixed_percpu_data底部。相关 per-cpu 变量还包括hardirq_stack_ptr、irq_count判断 CPU 是否已在中断栈上与softirq_stack_ptr其中irq_init_percpu_irqstack为每个 CPU 设置hardirq_stack_ptr。Part 2早期中断初始化——从 boot 到 start_kernel保护模式入口与 gs 寄存器初始化linux-interrupts-2.md 指出最早与中断相关的 x86_64 架构代码就是setup_idt它发生在进入保护模式之前。随后通过protected_mode_jumparch/x86/boot/pmjump.S跳转到 32 位入口再经压缩内核的head_64.S进入长模式。startup_64中通过wrmsr写模型特定寄存器MSR_GS_BASE地址0xc0000101把initial_gs指向init_per_cpu__irq_stack_union即中断栈底部加载进gsmovl $MSR_GS_BASE,%ecx movl initial_gs(%rip),%eax movl initial_gs4(%rip),%edx wrmsr长模式下 cs/ds/es/ss 的字段被忽略但可通过fs/gs访问内存MSR_GS_BASE就是这两段寄存器隐藏部分的后门提供 64 位基址。early_idt_handler_array早期 IDT 填充进入 C 代码的x86_64_start_kernel后会为前 32 个异常向量填充早期处理器for (i 0; i NUM_EXCEPTION_VECTORS; i) set_intr_gate(i, early_idt_handler_array[i]); load_idt((const struct desc_ptr *)idt_descr);其中NUM_EXCEPTION_VECTORS为 32EARLY_IDT_HANDLER_SIZE为 9——early_idt_handler_array是每 9 字节一个入口点的异常处理器数组定义在 arch/x86/kernel/head_64.S用.rept NUM_EXCEPTION_VECTORS填充最终转交给early_make_pgtable处理器其实现细节可参考 Initialization 章节 中「Early interrupt and exception handling」部分。设置中断栈 canary进入start_kernelinit/main.c后第一个与中断相关的是boot_init_stack_canary它依赖CONFIG_CC_STACKPROTECTOR未配置时为空函数。配置时先做编译期断言offsetof(union irq_stack_union, stack_canary) ! 40再基于随机数与 TSC 时间戳计算 canaryget_random_bytes(canary, sizeof(canary)); tsc __native_read_tsc(); canary tsc (tsc 32UL); this_cpu_write(irq_stack_union.stack_canary, canary);local_irq_disable / local_irq_enablelocal_irq_disable宏定义于 include/linux/irqflags.h依赖CONFIG_TRACE_IRQFLAGS_SUPPORT开启时额外调用trace_hardirqs_off记录 hardirq/softirq 开关事件lockdep 的 irq-flags tracing统计hardirqs_off_events、redundant_hardirqs_off等计数。两者共同的核心是raw_local_irq_disable展开为native_irq_disable的cli指令清除 IF 标志对应的local_irq_enable则用sti设置 IF 标志。注意老内核曾提供禁用所有处理器中断的cli该函数已被移除现在只有local_irq_{enabled,disable}用于禁用/启用当前处理器的中断。start_kernel中禁用本地中断后还会置early_boot_irqs_disabled true该标志用于smp_call_function_many等处的死锁检查WARN_ON_ONCE(cpu_online(this_cpu) irqs_disabled() !oops_in_progress !early_boot_irqs_disabled)。early_trap_init早期陷阱初始化setup_arch中第一个与中断相关的函数是early_trap_initarch/x86/kernel/traps.c它为 kgdb 早期调试填充#DB与#BP两个门void __init early_trap_init(void) { set_intr_gate_ist(X86_TRAP_DB, debug, DEBUG_STACK); set_system_intr_gate_ist(X86_TRAP_BP, int3, DEBUG_STACK); #ifdef CONFIG_X86_32 set_intr_gate(X86_TRAP_PF, page_fault); #endif load_idt(idt_descr); }三个设置函数的差异在于 DPLset_intr_gate_ist的 DPL 为 0内核态set_system_intr_gate_ist的 DPL 为 3用户态也可触发因为int3断点指令可在用户空间执行。它们最终都调用_set_gate→pack_gate填写 offset 三段、段选择符、IST、P 位、DPL、typewrite_idt_entrymemcpy到idt_table[entry]并先经BUG_ON((unsigned)n 0xFF)校验向量号上限。Part 3早期异常处理器——#DB 与 #BP 的实现linux-interrupts-3.md 深入剖析了这两个早期处理器的实现。#DB 与 #BP 异常#DBdebug调试事件发生时触发例如尝试修改调试寄存器。调试寄存器只能在内核特权级访问低特权级访问会引发通用保护故障所以用 DPL0 的set_intr_gate_ist注册。向量号 1无错误码。#BPbreakpoint执行int 3指令时触发可能发生在用户空间。文档给出了一个可复现的实验编译运行含内联int3的 C 程序直接运行会输出Trace/breakpoint trap在 gdb 下运行则可捕获SIGTRAP并继续执行。它用 DPL3 的set_system_intr_gate_ist注册向量号 3无错误码。idtentry 宏处理器入口的统一模板debug与int3的汇编入口定义在 arch/x86/entry/entry_64.Sidtentry debug do_debug has_error_code0 paranoid1 shift_istDEBUG_STACK idtentry int3 do_int3 has_error_code0 paranoid1 shift_istDEBUG_STACKidtentry宏接受sym全局入口符号、do_symC 二级处理器、has_error_code三个必选参数以及paranoid、shift_ist两个可选参数。其内部流程若异常不提供错误码压入伪错误码-1同时作为无效系统调用号防止系统调用重启逻辑被触发.ifeq \has_error_code pushq $-1 .endif用ALLOC_PT_GPREGS_ON_STACK在栈上分配 15×8 字节空间保存通用寄存器根据paranoid决定用快速还是慢速方式判断来自用户态还是内核态快速法testl $3, CS(%rsp)检查 CS 段选择符中的 CPL慢速法paranoid1用于 NMI/MCE/DEBUG 等超级原子入口上下文rdmsr读MSR_GS_BASE内核空间 gs 指向 per-cpu 区起点地址从0xffff880000000000开始%edx中为负值故testl %edx,%edx; js判断来自内核态调用error_entry用户态或paranoid_entry内核态完成寄存器保存与SWAPGS交换MSR_KERNEL_GS_BASE与MSR_GS_BASE使 gs 指向内核结构基址用户态场景下调用sync_regsarch/x86/kernel/traps.c把寄存器拷贝到当前任务的task_pt_regs并切换到常规内核栈然后以%rdi传pt_regs指针、%rsi传错误码调用do_sym若shift_ist ! -1用subq $EXCEPTION_STKSZ, CPU_TSS_IST(\shift_ist)切换到 IST 栈处理器结束后跳转error_exit判断来源、按需执行SWAPGS、恢复寄存器并以iret交还控制权。Part 4非早期中断门的初始化——page fault 与 trap_initlinux-interrupts-4.md 从早期页故障处理器讲起。早期页故障处理器early_trap_pf_initarch/x86/kernel/traps.c在 x86_64 上为向量 14X86_TRAP_PF注册page_faultvoid __init early_trap_pf_init(void) { #ifdef CONFIG_X86_64 set_intr_gate(X86_TRAP_PF, page_fault); #endif }其汇编入口用trace_idtentry page_fault do_page_fault has_error_code1定义CONFIG_TRACING开启时额外注册 trace 版本否则等价于普通idtentry。C 处理器do_page_faultarch/x86/mm/fault.c先read_cr2()读取引发页故障的线性地址再用exception_enter()/exception_exit()包裹 context trackingRCU 借此摆脱处理器运行于用户态时对定时器 tick 的依赖最后调用__do_page_fault。后者会检查 kmemcheck、prefetchw预取mm-mmap_sem缓存行并通过fault_in_kernel_space(address)即address TASK_SIZE_MAXTASK_SIZE_MAX (1UL 47) - PAGE_SIZE即0x00007ffffffff000判断故障来自内核态还是用户态再分派给 kmemcheck / spurious / kprobes 等对应处理路径。文中还介绍了likely/unlikely宏基于__builtin_expect的编译器优化原理。trap_init填充剩余异常门setup_arch之后回到start_kerneltrap_initarch/x86/kernel/traps.c负责剩余异常处理器的注册完整清单如下#DE除零错误set_intr_gate(X86_TRAP_DE, divide_error)#NMI非掩蔽中断set_intr_gate_ist(X86_TRAP_NMI, nmi, NMI_STACK)#OF溢出INTO 指令触发、#BRBOUND 越界、#UD无效操作码、#NM设备不可用cr0 的 EM 标志置位时执行 x87 FPU 指令set_system_intr_gate(X86_TRAP_OF, overflow)等#DF双重故障set_intr_gate_ist(X86_TRAP_DF, double_fault, DOUBLEFAULT_STACK)#CSO协处理器段越界、#TS无效 TSS、#NP段不存在、#SS栈故障STACKFAULT_STACK、#GP通用保护、#SPURIOUS伪中断、#MFx87 FPU 错误、#AC对齐检查#MC机器检查依赖CONFIG_X86_MCEMCE_STACK#XFSIMD 浮点异常SSE/SSE2/SSE3 的六类数值异常条件无效操作、除零、非规格化操作数、上溢、下溢、结果不精确。随后填充used_vectors位图FIRST_EXTERNAL_VECTOR为0x20即前 32 个向量标记为已用CONFIG_IA32_EMULATION开启时注册IA32_SYSCALL_VECTOR0x80ia32_syscall并把 IDT 映射到 fixmap 只读区域__set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO)最后调用cpu_init。cpu_initTSS 与 IST 的 per-cpu 初始化cpu_initarch/x86/kernel/cpu/common.c流程包括wait_for_master_cpu、cr4_init_shadow、load_ucode_ap清空 cr4 的X86_CR4_VME|X86_CR4_PVI|X86_CR4_TSD|X86_CR4_DE位switch_to_new_gdt与load_current_idt重载 GDT/IDT遍历N_EXCEPTION_STACKS4 个异常栈把exception_stacks地址填入t-x86_tss.ist[v]与oist-ist[v]DEBUG_STACK对应的地址额外存入debug_stack_addr再用set_tss_descload_TR_descltr指令加载 TSS 描述符。trap_init末尾会把idt_table拷贝到nmi_idt_table并重新用set_nmi_gate注册#DB/#BP——原因是早期在early_trap_init注册时 TSS 尚未就绪此时 TSS 已由cpu_init准备好需要重设才能正确使用 IST。Part 5异常处理器的实现——DO_ERROR 与 do_traplinux-interrupts-5.md 展示了 Part 4 注册的各异常处理器divide_error、overflow、invalid_op、bounds、device_not_available、coprocessor_segment_overrun、invalid_TSS、segment_not_present、stack_segment、spurious_interrupt_bug、coprocessor_error、alignment_check、simd_coprocessor_error的实现。它们由DO_ERROR宏生成#define DO_ERROR(trapnr, signr, str, name) \ dotraplinkage void do_##name(struct pt_regs *regs, long error_code) \ { \ do_error_trap(regs, error_code, str, trapnr, signr); \ }用法示例##为 GCC 宏拼接DO_ERROR(X86_TRAP_DE, SIGFPE, divide error, divide_error) DO_ERROR(X86_TRAP_OF, SIGSEGV, overflow, overflow) DO_ERROR(X86_TRAP_UD, SIGILL, invalid opcode, invalid_op) DO_ERROR(X86_TRAP_TS, SIGSEGV, invalid TSS, invalid_TSS) DO_ERROR(X86_TRAP_NP, SIGBUS, segment not present, segment_not_present) DO_ERROR(X86_TRAP_SS, SIGBUS, stack segment, stack_segment) DO_ERROR(X86_TRAP_AC, SIGBUS, alignment check, alignment_check)do_error_trap的核心流程exception_enter()/exception_exit()context tracking 子系统跟踪 user/kernel/guest 上下文切换notify_die(DIE_TRAP, str, regs, error_code, trapnr, signr)通过 notifier chaindie_chain一个原子通知链内含自旋锁与 notifier_block 链表通知内核 panic、oops、NMI 等事件监听者若不返回NOTIFY_STOP继续处理conditional_sti(regs)若被中断进程的X86_EFLAGS_IF置位则调用local_irq_enable()重新使能中断do_trap(trapnr, signr, str, regs, error_code)先经do_trap_no_signal检查是否来自虚拟 8086 模式或内核态——内核态故障若fixup_exception无法修复则填充thread.error_code/thread.trap_nr并调用diearch/x86/kernel/dumpstack.c打印栈、寄存器、模块信息并触发 oops用户态故障则向引发异常的进程发送对应信号如除零发SIGFPE、非法指令发SIGILL、段不存在发SIGBUS。处理器返回路径由paranoid_exit完成RESTORE_EXTRA_REGS、RESTORE_C_REGS、REMOVE_PT_GPREGS_FROM_STACK 8最后INTERRUPT_RETURN即jmp native_iret→iretq。Part 6非掩蔽中断NMI处理器linux-interrupts-6.md 指出NMI 是标准屏蔽技术无法忽略的硬件中断可通过两种方式产生外部硬件断言 CPU 的 NMI 引脚或处理器在系统总线 / APIC 串行总线上收到投递模式为 NMI 的消息。NMI 的向量号为 2注册时使用set_intr_gate_ist(X86_TRAP_NMI, nmi, NMI_STACK)。与其他异常处理器不同NMI 入口不使用idtentry宏而是在 arch/x86/entry/entry_64.S 中拥有独立入口ENTRY(nmi)。其特殊性在于 NMI 可能嵌套例如在swapgs临界区内再次触发因此必须用慢速的MSR_GS_BASE校验区分用户态/内核态并使用NMI_STACK独立栈。入口先经PARAVIRT_ADJUST_EXCEPTION_FRAME与半虚拟化相关保存rdx并通过cmpl $__KERNEL_CS, 16(%rsp)判断cs是否为内核段以处理 first_nmi 等嵌套场景。Part 6 还覆盖了 BOUND 越界、协处理器异常与 SIMD 协处理器异常等架构相关剩余处理器。Part 7 与 Part 8外部硬件中断的初始化linux-interrupts-7.md 讲解与外部硬件中断处理相关的早期初始化覆盖向量号从FIRST_EXTERNAL_VECTOR0x20起的 I/O 中断设置路径linux-interrupts-8.md 讲解外部硬件中断处理的非早期初始化承接init_IRQarch/x86/kernel/irqinit.c等代码为 Part 9 的延迟中断铺垫。这两部分共同构建了「外部设备 → I/O APIC → 本地 APIC → CPU」的中断分发链路初始化是理解request_irq/irqaction等上层机制的基础。Part 9延迟中断——Softirq、Tasklets 与 Workqueueslinux-interrupts-9.md 从中断处理器的两个矛盾特性出发处理器必须快速执行但有时又需要做大量工作。因此中断处理被拆成两部分Top half上半部主处理器只做最小且最关键的工作随后调度下半部并结束Bottom half下半部系统空闲且上下文允许时再完成剩余工作。该术语如今已成为「延迟中断处理」各种方式的统称。Linux 内核有三种延迟中断类型Softirq每个处理器都有一个名为ksoftirqd/n的内核线程n 为 CPU 编号由early_initcall(spawn_ksoftirqd)启动可用systemd-cgls -k | grep ksoft查看。Softirq 在编译期静态确定open_softirq(nr, action)负责初始化void open_softirq(int nr, void (*action)(struct softirq_action *)) { softirq_vec[nr].action action; }softirq_vec是含NR_SOFTIRQS10个元素的数组当前内核定义了十种软中断向量两个用于 tasklet 处理HI_SOFTIRQ、TASKLET_SOFTIRQ、两个用于网络NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、两个用于块层BLOCK_SOFTIRQ、BLOCK_IOPOLL_SOFTIRQ、两个用于定时器TIMER_SOFTIRQ、HRTIMER_SOFTIRQ以及调度器SCHED_SOFTIRQ和 RCURCU_SOFTIRQ各一个enum { HI_SOFTIRQ0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ, NR_SOFTIRQS };Tasklets 与 WorkqueuesTasklet 构建在 softirq 之上TASKLET_SOFTIRQ/HI_SOFTIRQ两个向量承载所有 tasklet适合快速、不可睡眠的延迟任务Workqueue 则运行在可睡眠的内核线程上下文中适合需要阻塞或长时间执行的延迟工作。三者构成从「快速不可睡眠」到「慢速可睡眠」的完整延迟中断谱系。Part 10真实硬件驱动——21285 串口驱动的中断实践linux-interrupts-10.md 以 StrongARM SA-110/21285 评估板的串口驱动drivers/tty/serial/21285.c为例串联全书所学驱动通过module_init(serial21285_init)/module_exit(serial21285_exit)注册作为可加载模块时这两个宏把初始化/退出函数别名到init_module/cleanup_module由 initcall 机制early_initcall、pure_initcall、core_initcall、postcore_initcall、arch_initcall、subsys_initcall、fs_initcall、rootfs_initcall、device_initcall、late_initcall在do_initcallsinit/main.c中调用静态编译进内核时则展开为__initcall(x)/__exitcall(x)serial21285_init打印驱动信息、调用serial21285_setup_ports设置 UART 基础时钟再uart_register_driver注册驱动并uart_add_one_port添加端口后续流程演示驱动如何请求 IRQ 线、中断触发后如何经 IDT 找到入口、最终进入串口中断处理器。学习路径建议建议按以下顺序阅读仓库文档以获得完整知识链理论筑基linux-interrupts-1.mdIDT、向量号、IST启动路径linux-interrupts-2.md → linux-interrupts-3.md → linux-interrupts-4.md初始化与早期处理器处理器实现linux-interrupts-5.md → linux-interrupts-6.md硬件中断linux-interrupts-7.md → linux-interrupts-8.md延迟处理与实战linux-interrupts-9.md → linux-interrupts-10.md。相关背景可交叉参考 Initialization 章节早期异常处理、TSS 相关初始化与 Booting 章节保护模式/长模式切换这些章节共同构成《linux-insides》对 x86_64 内核启动与中断子系统的完整叙述。赞分享文档教程操作系统【免费下载链接】linux-insidesA book-in-progress about the Linux kernel and its insides.项目地址https://gitcode.com/gh_mirrors/li/linux-insides点击查看免费下载相关推荐Linux 内核中断与中断处理一从中断理论到 IDT 与 x86_64 中断栈Linux 内核中断与中断处理一从中断理论到 IDT 与 x86_64 中断栈 本文是 linux insides 仓库中「Interrupts and文档教程操作系统Linux 内核揭秘中断与中断处理入门Part 1——从硬件事件到 IDT 与中断栈Linux 内核揭秘中断与中断处理入门Part 1——从硬件事件到 IDT 与中断栈 导读 中断interrupt是 Linux 内核与外部世界交互的Linux 内核初始化揭秘二早期中断与异常处理——从 IDT 建立到缺页中断处理器Linux 内核初始化揭秘二早期中断与异常处理——从 IDT 建立到缺页中断处理器 本篇技术指南聚焦于 x86_64 架构下 Linux 内核在 解压完成上一篇Buzz Mac 版安装指南三步跑起本地离线转录下一篇抖音下载器终极指南3步搞定批量下载免费保存视频音乐创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考