
Linux x86 IO-APICSMP 中断路由机制与 pirq 手工重定向实战【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux本文基于 Linux 内核文档 IO-APIC.rstIngo Molnar 著展开讲解 Intel-MP 兼容 SMP 主板上 IO-APIC 增强中断控制器的工作原理、/proc/interrupts的验证方法以及当主板 MP 表损坏时如何用pirq启动参数手工构造 PCI IRQ 路由条目并结合内核源码 arch/x86/kernel/apic/io_apic.c 印证参数解析、RTE 路由寄存器与 i8259 级联的实现细节。IO-APIC 是什么把中断从单核分散到多核绝大多数实际上所有Intel-MP 兼容的 SMP 主板都带有所谓IO-APIC——一个增强型中断控制器。它使硬件中断可以被路由到多个 CPU或路由到 CPU 组没有 IO-APIC 时硬件中断只会投递给启动操作系统的 CPU通常是 CPU#0。Linux 支持所有合规 SMP 主板的变体包括带多个 IO-APIC的主板。高端服务器使用多个 IO-APIC 来进一步分散 IRQ 负载。从源码结构看这一点体现在 io_apic.c 的静态数组与结构体上static struct ioapic { int nr_registers; /* IRQ 路由寄存器数量 */ struct IO_APIC_route_entry *saved_registers; /* 挂起/恢复或中断重映射时保存的状态 */ struct mpc_ioapic mp_config; /* 来自 MP 表的 IO-APIC 配置 */ struct mp_ioapic_gsi gsi_config; struct ioapic_domain_cfg irqdomain_cfg; struct irq_domain *irqdomain; struct resource *iomem_res; } ioapics[MAX_IO_APICS];每个 IO-APIC 实例记录其寄存器pin数量、MP 表配置、GSI 路由区间和 irq domain内核用for_each_ioapic(idx)宏在 0 到nr_ioapics之间遍历所有 IO-APIC。值得注意的是某些较老主板上存在已知的硬件缺陷这类 bug 通常由内核自行规避workaround。如果你的 MP 兼容 SMP 主板无法启动 Linux建议先查阅 linux-smp 邮件列表存档——原文档即以此作为排查起点。验证 IO-APIC 是否正常工作/proc/interrupts如果你的机器启用了 IO-APIC 中断且能正常启动那么/proc/interrupts应当形如原文档给出的样例hell:~ cat /proc/interrupts CPU0 0: 1360293 IO-APIC-edge timer 1: 4 IO-APIC-edge keyboard 2: 0 XT-PIC cascade 13: 1 XT-PIC fpu 14: 1448 IO-APIC-edge ide0 16: 28232 IO-APIC-level Intel EtherExpress Pro 10/100 Ethernet 17: 51304 IO-APIC-level eth0 NMI: 0 ERR: 0 hell:~解读要点IO-APIC-edge/IO-APIC-level中断经由 IO-APIC 投递分别表示边沿触发与电平触发。网络设备如以太网通常是电平触发。XT-PIC仍走传统 8259 可编程中断控制器的中断。少量中断如cascade级联、fpu协处理器异常留在 XT PIC 上没有问题——这些中断源都不在性能关键路径上。级联机制在内核中同样有对应逻辑io_apic.c 中的enable_IO_APIC()会扫描所有 IO-APIC pin找出处于 ExtINTExternal Interrupt模式的 pin从而定位 8259 接入 IO-APIC 的位置若硬件中未配置 ExtINT 而 MP 表报告了则回退信任 MP 表两者不一致时打印ExtINT in hardware and MP table differ警告。最后通过clear_IO_APIC()清空所有路由表项——内核不信任启动时 IO-APIC 里的残留状态。路由寄存器 RTE中断投递的底层配置IO-APIC 对每个中断 pin 用一个 64 位**路由表项RTE, Route Table Entry**描述投递行为。内核在 arch/x86/include/asm/io_apic.h 中给出了完整的位域定义struct IO_APIC_route_entry { union { struct { u64 vector : 8, /* 中断向量 */ delivery_mode : 3, /* 投递模式含 ExtINT/Fixed 等 */ dest_mode_logical : 1, /* 0物理, 1逻辑目标 */ delivery_status : 1, active_low : 1, /* 低有效 */ irr : 1, is_level : 1, /* 电平触发 */ masked : 1, /* 屏蔽位 */ reserved_0 : 15, reserved_1 : 17, virt_destid_8_14 : 7, destid_0_7 : 8; /* 目标 CPU 的 APIC ID */ }; ... }; } __attribute__ ((packed));从字段含义可以印证前文概念destid_*决定中断投递到哪个 CPU即路由到多个 CPU的落点masked/is_level/active_low决定 pin 是否可用及其触发极性delivery_mode中的 ExtINT 模式正是 8259 级联所用的投递方式。主板 MP 表损坏时用 pirq 手工构造 IRQ 条目在极小概率的情况下你的主板不会生成可用的 MP 表MP table。此时可以用pirq启动参数手工构造IRQ 条目。这不是一个可以自动化的简单操作原文档以 LILO 配置为例appendpirq15,11,10具体数值取决于你的系统、你的 PCI 卡以及它们的 PCI 插槽位置。PCI 槽位的菊花链daisy chain结构通常 PCI 槽位在接入 PCI 芯片组 IRQ 路由设施PIRQ1-4 输入线之前会先做菊花链串联,-. ,-. ,-. ,-. ,-. PIRQ4 ----| |-. ,-| |-. ,-| |-. ,-| |--------| | |S| \ / |S| \ / |S| \ / |S| |S| PIRQ3 ----|l|-. /---|l|-. /---|l|-. /---|l|--------|l| |o| \/ |o| \/ |o| \/ |o| |o| PIRQ2 ----|t|-./----|t|-./----|t|-./----|t|--------|t| |1| /\ |2| /\ |3| /\ |4| |5| PIRQ1 ----| |- ----| |- ----| |- ----| |--------| | - - - - -每个 PCI 卡都发出一条 PCI IRQ可能是 INTA、INTB、INTC 或 INTD 四条线之一,-. INTD--| | |S| INTC--|l| |o| INTB--|t| |x| INTA--| | -这些 INTA-D 信号总是卡内局部的其真实含义取决于卡所在的槽位。对照菊花链图槽位 4 的卡若发出 INTA最终会落到 PCI 芯片组的 PIRQ4 上。大多数卡发出的是 INTA这会在 PIRQ 线之间形成最优的分布合理分配 IRQ 源并非硬性要求——PCI IRQ 本来就可以随意共享但非共享中断对性能更有利。槽位 5 一般留给显卡显卡通常不使用中断因此也不参与菊花链。组合示例如何得出你的 pirq 行假设你的 SCSI 卡IRQ11在槽 1Tulip 网卡IRQ9在槽 2那么需要指定appendpirq11,9原文档还提供了一个从 PCI 配置自动猜测默认 pirq 行的脚本scanpci属于已下线的旧工具仅作示意echo -n pirq; echo scanpci | grep T_L | cut -c56- | sed s/ /,/g注意如果中间跳过了若干槽位或者主板没有采用默认菊花链又或者 IO-APIC 的 PIRQ 引脚被接得很奇怪该脚本会失效。例如在上面场景中 SCSI 卡IRQ11实际在槽 3、而槽 1 是空的则应写appendpirq0,9,11其中0 是一个通用占位符表示空槽位或不发 IRQ 的槽位。一般而言通过正确地全排列所有 IRQ 编号总归能找到正确的 pirq 配置——只是比较费时间。一条不正确的 pirq 行会导致启动过程挂起或某个设备不能正常工作例如该设备以模块形式加载时。如果主板有 2 条 PCI 总线最多可以使用8 个pirq 值不过这类主板通常配置得比较好。也可能遇到一些奇怪的 pirq 行appendpirq0,0,0,0,0,0,9,11用明智的试错法找出正确的 pirq 行即可。若本文档未覆盖你遇到的问题可致信 linux-smpvger.kernel.org 或 linux-kernelvger.kernel.org。源码印证pirq 的解析与生效位置pirq在内核中的解析入口是 io_apic.c 中的ioapic_pirq_setup()通过__setup(pirq, ...)注册#ifdef CONFIG_X86_32 /* * support for broken MP BIOSs, enables hand-redirection of PIRQ0-7 to * specific CPU-side IRQs. */ #define MAX_PIRQS 8 static int pirq_entries[MAX_PIRQS] { [0 ... MAX_PIRQS - 1] -1 }; static int __init ioapic_pirq_setup(char *str) { int i, max, ints[MAX_PIRQS1]; get_options(str, ARRAY_SIZE(ints), ints); apic_pr_verbose(PIRQ redirection, working around broken MP-BIOS.\n); max MAX_PIRQS; if (ints[0] MAX_PIRQS) max ints[0]; for (i 0; i max; i) { apic_pr_verbose(... PIRQ%d - IRQ %d\n, i, ints[i 1]); /* PIRQs are mapped upside down, usually */ pirq_entries[MAX_PIRQS-i-1] ints[i1]; } return 1; } __setup(pirq, ioapic_pirq_setup); #endif /* CONFIG_X86_32 */由此可以确认几个与原文档相互印证的事实上限 8 个值MAX_PIRQS 8对应2 条 PCI 总线最多 8 个 pirq 值的说法第一条 PCI 总线 4 条 PIRQ 线 × 2 条总线。反转映射注释PIRQs are mapped upside down, usually表明第一个命令行数字对应最高编号的 PIRQ 线——这正是需要按槽位顺序仔细推导的原因。适用前提整段代码包在#ifdef CONFIG_X86_32中即pirq是 32 位 x86 内核专属的手工重定向机制适用于broken MP BIOS场景64 位内核没有这条路径。生效点在pin_2_irq()中当 pin 落在 16~23 区间即 PCI 芯片组接入 IO-APIC 的 PIRQ 线且对应pirq_entries被赋值时内核直接返回重定向目标 IRQ值为 0 则打印Disabling PIRQn并禁用该线否则才走 MP 表映射mp_map_pin_to_irq()。这解释了为何错误的 pirq 行会导致设备中断彻底丢失、模块加载的设备不能正常工作。此外驱动通过IO_APIC_get_PCI_irq_vector(bus, slot, pin)同为 io_apic.c 中导出符号查询 PCI 设备到 Linux IRQ 号的映射该函数遍历 MP 表 IRQ source 条目并按 srcbusirq 中的设备号/INT 引脚匹配找不到精确匹配时还保留除 pin 外全部匹配的模糊结果以容忍损坏的 MP 表——这与文档中broken mptables的容忍策略一脉相承。相关启动参数小结参数作用适用前提pirqN,N,...最多 8 个MP 表损坏时手工将 PIRQ0-7 重定向到指定 CPU 侧 IRQ0 为占位符仅 CONFIG_X86_3232 位 x86 内核noapic完全禁用 IO-APIC 支持early_param置ioapic_is_disabled所有 x86 内核排查顺序建议先用/proc/interrupts确认中断是否由IO-APIC-*投递若设备无中断计数再怀疑 MP 表最后才考虑pirq试错——每条错误的 pirq 行都可能直接挂起启动务必逐槽位推导。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考