/** * struct irqaction - per interrupt action descriptor * @handler: interrupt handler function * @name: name of the device * @dev_id: cookie to identify the device * @percpu_dev_id: cookie to identify the device * @next: pointer to the next irqaction for shared interrupts * @irq: interrupt number * @flags: flags (see IRQF_* above) * @thread_fn: interrupt handler function for threaded interrupts * @thread: thread pointer for threaded interrupts * @secondary: pointer to secondary irqaction (force threading) * @thread_flags: flags related to @thread * @thread_mask: bitmask for keeping track of @thread activity * @dir: pointer to the proc/irq/NN/name entry */struct irqaction {irq_handler_t handler;void*dev_id;void __percpu *percpu_dev_id;struct irqaction *next;irq_handler_t thread_fn;struct task_struct *thread;struct irqaction *secondary;unsignedint irq;unsignedint flags;unsignedlong thread_flags;unsignedlong thread_mask;constchar*name;struct proc_dir_entry *dir;} ____cacheline_internodealigned_in_smp;
众多的中断irq_desc则采取类似于内存管理中所用到的基数树radix tree的方式进行管理。这种结构对于从某个整型 key 找到 value 速度很快,中断信号 irq 是这个整数。通过它,我们很快就能定位到对应的 irq_desc。
我们从 CPU 收到中断向量开始分析.CPU收到的中断向量定义于irq_vectors.h。下面这一段是该头文件的注释,详细描述了IRQ向量的基本信息:
单个CPU拥有256(8位)IDT,即能处理256个中断,定义为NR_VECTORS
CPU处理的中断分为几类
0到31位为系统陷入或者异常,这些属于无法屏蔽的中断,必须进行处理
32到127位为设备中断
128位即我们常说的int80系统调用中断
129至INVALIDATE_TLB_VECTOR_START也用来保存设备中断
INVALIDATE_TLB_VECTOR_START至255作为特殊中断
64位架构下每个CPU有独立的IDT表,而32位则共享一张表
/* * Linux IRQ vector layout. * * There are 256 IDT entries (per CPU - each entry is 8 bytes) which can * be defined by Linux. They are used as a jump table by the CPU when a * given vector is triggered - by a CPU-external, CPU-internal or * software-triggered event. * * Linux sets the kernel code address each entry jumps to early during * bootup, and never changes them. This is the general layout of the * IDT entries: * * Vectors 0 ... 31 : system traps and exceptions - hardcoded events * Vectors 32 ... 127 : device interrupts * Vector 128 : legacy int80 syscall interface * Vectors 129 ... INVALIDATE_TLB_VECTOR_START-1 except 204 : device interrupts * Vectors INVALIDATE_TLB_VECTOR_START ... 255 : special interrupts * * 64-bit x86 has per CPU IDT tables, 32-bit has one shared IDT table. * * This file enumerates the exact layout of them: */#defineIRQ_MOVE_CLEANUP_VECTOR FIRST_EXTERNAL_VECTOR#defineIA32_SYSCALL_VECTOR0x80#defineNR_VECTORS256#defineFIRST_SYSTEM_VECTOR NR_VECTORS
void __init trap_init(void){ /* Init cpu_entry_area before IST entries are set up */setup_cpu_entry_areas();idt_setup_traps(); /* * Set the IDT descriptor to a fixed read-only location, so that the * "sidt" instruction will not leak the location of the kernel, and * to defend the IDT against arbitrary memory write vulnerabilities. * It will be reloaded in cpu_init() */cea_set_pte(CPU_ENTRY_AREA_RO_IDT_VADDR, __pa_symbol(idt_table), PAGE_KERNEL_RO);idt_descr.address = CPU_ENTRY_AREA_RO_IDT; /* * Should be a barrier for any external CPU state: */cpu_init();idt_setup_ist_traps();x86_init.irqs.trap_init();idt_setup_debugidt_traps();}
/** * idt_setup_traps - Initialize the idt table with default traps */void __init idt_setup_traps(void){idt_setup_from_table(idt_table, def_idts, ARRAY_SIZE(def_idts),true);}/* * The exceptions which use Interrupt stacks. They are setup after * cpu_init() when the TSS has been initialized. */staticconst __initconst struct idt_data ist_idts[]= {ISTG(X86_TRAP_DB, debug, DEBUG_STACK),ISTG(X86_TRAP_NMI, nmi, NMI_STACK),ISTG(X86_TRAP_DF, double_fault, DOUBLEFAULT_STACK),#ifdefCONFIG_X86_MCEISTG(X86_TRAP_MC,&machine_check, MCE_STACK),#endif};/* * The default IDT entries which are set up in trap_init() before * cpu_init() is invoked. Interrupt stacks cannot be used at that point and * the traps which use them are reinitialized with IST after cpu_init() has * set up TSS. */staticconst __initconst struct idt_data def_idts[]= {INTG(X86_TRAP_DE, divide_error),INTG(X86_TRAP_NMI, nmi),INTG(X86_TRAP_BR, bounds),INTG(X86_TRAP_UD, invalid_op),INTG(X86_TRAP_NM, device_not_available),INTG(X86_TRAP_OLD_MF, coprocessor_segment_overrun),INTG(X86_TRAP_TS, invalid_TSS),INTG(X86_TRAP_NP, segment_not_present),INTG(X86_TRAP_SS, stack_segment),INTG(X86_TRAP_GP, general_protection),INTG(X86_TRAP_SPURIOUS, spurious_interrupt_bug),INTG(X86_TRAP_MF, coprocessor_error),INTG(X86_TRAP_AC, alignment_check),INTG(X86_TRAP_XF, simd_coprocessor_error),#ifdefCONFIG_X86_32TSKG(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS),#elseINTG(X86_TRAP_DF, double_fault),#endifINTG(X86_TRAP_DB, debug),#ifdefCONFIG_X86_MCEINTG(X86_TRAP_MC,&machine_check),#endifSYSG(X86_TRAP_OF, overflow),#ifdefined(CONFIG_IA32_EMULATION)SYSG(IA32_SYSCALL_VECTOR, entry_INT80_compat),#elifdefined(CONFIG_X86_32)SYSG(IA32_SYSCALL_VECTOR, entry_INT80_32),#endif};
void __init init_IRQ(void){int i; /* * On cpu 0, Assign ISA_IRQ_VECTOR(irq) to IRQ 0..15. * If these IRQ's are handled by legacy interrupt-controllers like PIC, * then this configuration will likely be static after the boot. If * these IRQ's are handled by more mordern controllers like IO-APIC, * then this vector space can be freed and re-used dynamically as the * irq's migrate etc. */for (i =0; i <nr_legacy_irqs(); i++)per_cpu(vector_irq,0)[ISA_IRQ_VECTOR(i)] =irq_to_desc(i);x86_init.irqs.intr_init();} .irqs = { .pre_vector_init = init_ISA_irqs, .intr_init = native_init_IRQ, .trap_init = x86_init_noop, .intr_mode_init = apic_intr_mode_init},void __init native_init_IRQ(void){ /* Execute any quirks before the call gates are initialised: */x86_init.irqs.pre_vector_init();idt_setup_apic_and_irq_gates();lapic_assign_system_vectors();if (!acpi_ioapic &&!of_ioapic &&nr_legacy_irqs())setup_irq(2,&irq2);irq_ctx_init(smp_processor_id());}
ENTRY(irq_entries_start) vector=FIRST_EXTERNAL_VECTOR .rept (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR) pushl $(~vector+0x80) /* Note: always in signed byte range */ vector=vector+1 jmp common_interrupt /* 会调用到do_IRQ */ .align 8 .endrEND(irq_entries_start)common_interrupt: ASM_CLAC addq $-0x80, (%rsp) /* Adjust vector to [-256, -1] range */ interrupt do_IRQ /* 0(%rsp): old RSP */ret_from_intr:...... /* Interrupt came from user space */GLOBAL(retint_user)....../* Returning to kernel space */retint_kernel:......
do_IRQ()从 AX 寄存器里面拿到了中断向量 vector,但是别忘了中断控制器发送给每个 CPU 的中断向量都是每个 CPU 局部的,而抽象中断处理层的虚拟中断信号 irq 以及它对应的中断描述结构 irq_desc 是全局的,也即这个 CPU 的 200 号的中断向量和另一个 CPU 的 200 号中断向量对应的虚拟中断信号 irq 和中断描述结构 irq_desc 可能不一样,这就需要一个映射关系。这个映射关系放在 Per CPU 变量 vector_irq 里面。
/* * do_IRQ handles all normal device IRQ's (the special * SMP cross-CPU interrupts have their own specific * handlers). */__visible unsignedint __irq_entry do_IRQ(struct pt_regs *regs){struct pt_regs *old_regs =set_irq_regs(regs);struct irq_desc * desc; /* high bit used in ret_from_ code */unsigned vector =~regs->orig_ax;...... desc =__this_cpu_read(vector_irq[vector]);if (!handle_irq(desc, regs)) {...... }......set_irq_regs(old_regs);return1;}DECLARE_PER_CPU(vector_irq_t, vector_irq);
boolhandle_irq(struct irq_desc *desc,struct pt_regs *regs){......generic_handle_irq_desc(desc);......}/* * Architectures call this to let the generic IRQ layer * handle an interrupt. */staticinlinevoidgeneric_handle_irq_desc(struct irq_desc *desc){desc->handle_irq(desc);}