本文是《Linux中断子系统》系列第3章的第三篇,深入讲解Linux中断子系统的初始化流程。从start_kernel出发,依次分析trap_init(注册异常处理钩子)、early_irq_init(初始化irq_desc数组或radix tree,支持稀疏IRQ)和init_IRQ(调用irqchip_init → of_irq_init解析设备树,触发gic_of_init回调)三个关键初始化函数。随后详细分析gic_init_bases的主要步骤:读取GICD_TYPER、调用irq_domain_create_tree创建GIC IRQ Domain、设置中断处理入口gic_handle_irq、初始化Distributor/CPU Interface/ITS等。最后介绍ARM V8 GIC-V3中三个IRQ Domain(GIC Domain、ITS Domain、MSI IRQ Domain)的层级关系及其在dmesg和/proc/interrupts中的体现。


kernel_5.15\init\main.c
Linux系统中,中断管理系统的初始化主要由几个函数来完成。在系统初始化的start_kernel()函数(在文件init/main.c中定义)中可以看到:
asmlinkage void __init start_kernel(void){……trap_init();……early_irq_init();init_IRQ();……}
start_kernel()函数调用trap_init()、early_irq_init()和init_IRQ()三个函数来初始化中断管理系统。
trap_init 不同平台实现不一样,以ARM64为例:
kernel_5.15\arch\arm64\kernel\traps.c
void __inittrap_init(void){register_kernel_break_hook(&bug_break_hook);register_kernel_break_hook(&fault_break_hook);#ifdef CONFIG_KASAN_SW_TAGSregister_kernel_break_hook(&kasan_break_hook);#endifdebug_traps_init();}
trap_init 函数的主要作用包括:为每一种异常类型(如除零异常、页故障等)注册相应的异常处理函数,以便在发生异常时能够正确地处理并恢复系统状态。
early_irq_init 和 init_IRQ 的区别和联系:
early_irq_init
功能描述:early_irq_init函数的主要任务是在内核启动的早期阶段,对中断处理机制进行基本的初始化。此时,内核可能还没有完全准备好处理所有高级功能或硬件抽象,因此early_irq_init的初始化工作相对基础但至关重要。
执行内容:
特点:由于early_irq_init在内核启动的早期阶段被调用,因此它需要尽可能避免依赖那些尚未初始化的内核子系统或硬件功能。
初始化irq_desc结构:irq_desc是Linux中断机制的核心数据结构,用于描述中断线(或中断源)。在early_irq_init函数中,会初始化这个结构的一些基本字段,为后续的中断处理做准备。
设置默认的中断亲和性:决定中断应该被哪个CPU核心处理。
打印中断数量信息:函数会打印出系统中可用的中断数量(NR_IRQS)。
初始化中断描述符数组:遍历irq_desc数组,为每个描述符设置一些基本的属性。
init_IRQ
功能描述:init_IRQ函数负责在内核启动的稍后期阶段,对中断处理系统进行更完整和复杂的初始化。
执行内容:
配置硬件中断控制器:根据具体的硬件平台,配置和初始化中断控制器,包括设置中断触发方式、中断优先级等。
初始化设备中断:对于每个连接到系统的设备,初始化其对应的中断处理逻辑。
建立中断处理程序链:建立多个处理程序之间的链接关系,确保当中断发生时,能够按照正确的顺序执行处理程序。
处理特殊的中断向量:对某些特殊的中断向量进行特殊处理,以确保它们的行为符合预期。
综上所述,early_irq_init负责基本的初始化工作,为后续的启动过程和中断处理提供必要的支持;而init_IRQ则负责更完整和复杂的初始化任务,确保中断处理系统的完整性和稳定性。
在start_kernel()函数中调用了early_irq_init()函数,这个函数在kernel/handle.c文件中定义。根据内核配置时是否选择了CONFIG_SPARSE_IRQ,会选择两个不同版本的early_irq_init()中的一个进行编译。CONFIG_SPARSE_IRQ配置项用于支持稀疏irq号,允许定义一个高CONFIG_NR_CPUS值但仍然不希望消耗太多内存的情况。
early_irq_init 主要工作即为初始化用于管理中断的irq_desc[NR_IRQS]数组的每个元素,主要实现如下:
#ifdef CONFIG_SPARSE_IRQint __init early_irq_init(void){int i, initcnt, node = first_online_node;struct irq_desc *desc;init_irq_default_affinity();/* Let arch update nr_irqs and return the nr of preallocated irqs */initcnt = arch_probe_nr_irqs();printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n",NR_IRQS, nr_irqs, initcnt);if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS))nr_irqs = IRQ_BITMAP_BITS;if (WARN_ON(initcnt > IRQ_BITMAP_BITS))initcnt = IRQ_BITMAP_BITS;if (initcnt > nr_irqs)nr_irqs = initcnt;for (i = 0; i < initcnt; i++) {desc = alloc_desc(i, node, 0, NULL, NULL);set_bit(i, allocated_irqs);irq_insert_desc(i, desc);}return arch_early_irq_init();}#else/* !CONFIG_SPARSE_IRQ */int __init early_irq_init(void){int count, i, node = first_online_node;struct irq_desc *desc;init_irq_default_affinity();printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS);desc = irq_desc;count = ARRAY_SIZE(irq_desc);for (i = 0; i < count; i++) {desc[i].kstat_irqs = alloc_percpu(unsigned int);alloc_masks(&desc[i], node);raw_spin_lock_init(&desc[i].lock);lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);mutex_init(&desc[i].request_mutex);desc_set_defaults(i, &desc[i], node, NULL, NULL);}return arch_early_irq_init();}
init_IRQ(void)函数是一个特定于体系结构的函数,以ARM64为例:
kernel_5.15\arch\arm64\kernel\irq.c
void __init init_IRQ(void){init_irq_stacks(); // 初始化 irq stacks 大小init_irq_scs();irqchip_init(); // 核心实现if (system_uses_irq_prio_masking()) {WARN_ON(read_sysreg(daif) & PSR_A_BIT);local_daif_restore(DAIF_PROCCTX_NOIRQ);}}
irqchip_init 主要实现如下:
void __init irqchip_init(void){of_irq_init(__irqchip_of_table);acpi_probe_device_table(irqchip);}
of_irq_init(__irqchip_of_table) 负责遍历设备树,查找所有中断控制器节点并触发其初始化回调。

针对 ARM V8 中GIC-V3的设备树文件:
/** ARM Ltd.* ARMv8 Foundation model DTS (GICv3 configuration)*// {gic: interrupt-controller@2f000000 {compatible = "arm,gic-v3";#interrupt-cells = <3>;#address-cells = <1>;#size-cells = <1>;ranges = <0x0 0x0 0x2f000000 0x100000>;interrupt-controller;reg = <0x0 0x2f000000 0x0 0x10000>,<0x0 0x2f100000 0x0 0x200000>,<0x0 0x2c000000 0x0 0x2000>,<0x0 0x2c010000 0x0 0x2000>,<0x0 0x2c02f000 0x0 0x2000>;interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;its: msi-controller@2f020000 {compatible = "arm,gic-v3-its";msi-controller;#msi-cells = <1>;reg = <0x20000 0x20000>;};};};
compatible:用于匹配GICv3驱动
reg:GIC的物理基地址,分别对应GICD、GICR、GICC…
interrupt-controller:表示该节点是一个中断控制器
初始化
kernel_5.15\drivers\irqchip\irq-gic-v3.c
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);定义 IRQCHIP_DECLARE 之后,相应的内容会保存到 __irqchip_of_table 里边:
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)#define OF_DECLARE_2(table, name, compat, fn) \_OF_DECLARE(table, name, compat, fn, of_init_fn_2)#define _OF_DECLARE(table, name, compat, fn, fn_type) \static const struct of_device_id __of_table_##name \__used __section(__##table##_of_table) \= { .compatible = compat, \.data = (fn == (fn_type)NULL) ? fn : fn }
__irqchip_of_table 在链接脚本 vmlinux.lds 里,被放到了 __irqchip_begin 和 __irqchip_of_end 之间:
#ifdef CONFIG_IRQCHIP #define IRQCHIP_OF_MATCH_TABLE() \ . = ALIGN(8); \ VMLINUX_SYMBOL(__irqchip_begin) = .; \ *(__irqchip_of_table) \ *(__irqchip_of_end)#endif
在内核启动初始化中断的函数中,of_irq_init 函数会去查找设备节点信息,该函数的传入参数就是 __irqchip_of_table 段。of_irq_init 函数会根据 "arm,gic-v3" 去查找对应的设备节点,并获取设备的信息,最终会回调 IRQCHIP_DECLARE 声明的回调函数,也就是 gic_of_init,而这个函数就是 GIC 驱动的初始化入口。
#ifdef CONFIG_IRQCHIP#define IRQCHIP_OF_MATCH_TABLE() \. = ALIGN(8); \VMLINUX_SYMBOL(__irqchip_begin) = .; \*(__irqchip_of_table) \*(__irqchip_of_end)#endif
通过调试可以打印:

gic_of_init 流程——核心的处理函数就是 gic_init_bases:
void __initof_irq_init(conststruct of_device_id *matches){const struct of_device_id *match;struct device_node *np, *parent = NULL;struct of_intc_desc *desc, *temp_desc;struct list_head intc_desc_list, intc_parent_list;INIT_LIST_HEAD(&intc_desc_list);INIT_LIST_HEAD(&intc_parent_list);for_each_matching_node_and_match(np, matches, &match){if (!of_property_read_bool(np, "interrupt-controller") ||!of_device_is_available(np))continue;....desc->irq_init_cb = match->data;desc->dev = of_node_get(np);desc->interrupt_parent = of_irq_find_parent(np);....}....while (!list_empty(&intc_desc_list)) {ret = desc->irq_init_cb(desc->dev,desc->interrupt_parent);.....}}
static int __initgic_init_bases(void __iomem *dist_base,struct redist_region *rdist_regs,u32 nr_redist_regions,u64 redist_stride,struct fwnode_handle *handle){u32 typer;int err;if (!is_hyp_mode_available())static_branch_disable(&supports_deactivate_key);if (static_branch_likely(&supports_deactivate_key))pr_info("GIC: Using split EOI/Deactivate mode\n");gic_data.fwnode = handle;gic_data.dist_base = dist_base;gic_data.redist_regions = rdist_regs;gic_data.nr_redist_regions = nr_redist_regions;gic_data.redist_stride = redist_stride;/* 确认支持 SPI 中断号最大的值为多少。*/typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);gic_data.rdists.gicd_typer = typer;gic_enable_quirks(readl_relaxed(gic_data.dist_base + GICD_IIDR),gic_quirks, &gic_data);pr_info("%d SPIs implemented\n", GIC_LINE_NR - 32);pr_info("%d Extended SPIs implemented\n", GIC_ESPI_NR);if (!(gic_data.flags & FLAGS_WORKAROUND_CAVIUM_ERRATUM_38539))gic_data.rdists.gicd_typer2 = readl_relaxed(gic_data.dist_base + GICD_TYPER2);/* 向系统中注册一个 irq domain 的数据结构,irq_domain 主要作用是将硬件中断号映射到 irq number。*/gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,&gic_data);gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));gic_data.rdists.has_rvpeid = true;gic_data.rdists.has_vlpis = true;gic_data.rdists.has_direct_lpi = true;gic_data.rdists.has_vpend_valid_dirty = true;if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) {err = -ENOMEM;goto out_free;}irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);gic_data.has_rss = !!(typer & GICD_TYPER_RSS);pr_info("Distributor has %sRange Selector support\n",gic_data.has_rss ? "" : "no ");if (typer & GICD_TYPER_MBIS) {err = mbi_init(handle, gic_data.domain);if (err)pr_err("Failed to initialize MBIs\n");}/* 设定 arch 相关的 irq handler。*/set_handle_irq(gic_handle_irq);gic_update_rdist_properties();/* 初始化 Distributor。*/gic_dist_init();/* 初始化 CPU interface。*/gic_cpu_init();/* 设置 SMP 核间交互的回调函数,用于 IPI,回调函数为 gic_raise_softirq。*/gic_smp_init();/* 初始化 GIC 电源管理。*/gic_cpu_pm_init();gic_syscore_init();/* 初始化 ITS。*/if (gic_dist_supports_lpis()) {its_init(handle, &gic_data.rdists, gic_data.domain);its_cpu_init();} else {if (IS_ENABLED(CONFIG_ARM_GIC_V2M))gicv2m_init(handle, gic_data.domain);}gic_enable_nmi_support();return 0;out_free:if (gic_data.domain)irq_domain_remove(gic_data.domain);free_percpu(gic_data.rdists.rdist);return err;}
gic_init_bases 函数重点分析:
确认支持SPI中断号最大的值。GICv3最多支持1020个中断(SPI+SGI+PPI)。GICD_TYPER寄存器bit[4:0],如果该字段的值为N,则最大SPI INTID为32(N+1)-1。
调用irq_domain_create_tree向系统中注册一个irq domain的数据结构,重要参数为gic_irq_domain_ops和gic_data,irq_domain主要作用是将硬件中断号映射到IRQ number。
用于区分MSI域,比如一个域用作PCI/MSI,一个域用作wired IRQs。
判断GICD是否支持RSS(Range Selector Support),表示SGI中断亲和性的范围。
判断是否支持通过写GICD寄存器生成消息中断(GICD_TYPER寄存器bit[16])。
设置handle_arch_irq为gic_handle_irq。在kernel发生中断后,会跳转到汇编代码entry-armv.S中__irq_svc处,进而调用handle_arch_irq,从而进入GIC驱动进行后续的中断处理。
初始化ITS(Interrupt Translation Service),用来解析LPI中断。初始化之前需要先判断GIC是否支持LPI,该功能在ARM里是可选的。
gic_smp_init主要包含两个作用:触发SGI中断用于CPU之间通信(GICv3的回调函数定义为gic_raise_softirq);设置CPU上下线流程中和GIC相关的状态机。
以ARM V8为例进行代码分析,在ARM V8 GIC-V3中总共创建了三个中断域:ARM-GIC-V3 IRQ Domain、ITS Domain和MSI IRQ Domain,三者关系如下:

通过dmesg打印信息可以看到三者之间的关系:

针对54号中断:

handler: handle_fasteoi_irqdevice: 0000:00:02.0status: 0x00000000istate: 0x00000000ddepth: 0wdepth: 0dstate: 0x33400200IRQD_ACTIVATEDIRQD_IRQ_STARTEDIRQD_SINGLE_TARGETIRQD_AFFINITY_ON_ACTIVATEIRQD_DEFAULT_TRIGGER_SETIRQD_HANDLE_ENFORCE_IRQCTXnode: -1affinity: 0-3effectiv: 0domain: :intc@8000000:its@8080000-3hwirq: 0x8000chip: ITS-MSIflags: 0x20IRQCHIP_ONESHOT_SAFEparent:domain: :intc@8000000:its@8080000-5hwirq: 0x2000chip: ITSflags: 0x0parent:domain: :intc@8000000-1hwirq: 0x2000chip: GICv3flags: 0x15IRQCHIP_SET_TYPE_MASKEDIRQCHIP_MASK_ON_SUSPENDIRQCHIP_SKIP_SET_WAKE
GICv3 domain 创建

ITS Domain 注册过程
gic_init_bases|- its_init(handle, &gic_data.rdists, gic_data.domain);|- its_of_probe(of_node);|- its_probe_one(&res, &np->fwnode, of_node_to_nid(np));|- irq_domain_create_tree(handle, &its_domain_ops, its);staticintits_init_domain(struct fwnode_handle *handle, struct its_node *its){struct irq_domain *inner_domain;struct msi_domain_info *info;info = kzalloc(sizeof(*info), GFP_KERNEL);if (!info)return -ENOMEM;inner_domain = irq_domain_create_tree(handle, &its_domain_ops, its);if (!inner_domain) {kfree(info);return -ENOMEM;}inner_domain->parent = its_parent;irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS);inner_domain->flags |= its->msi_domain_flags;info->ops = &its_msi_domain_ops;info->data = its;inner_domain->host_data = info;return 0;}static const struct of_device_id its_device_id[] = {{ .compatible = "arm,gic-v3-its", },{},};static const struct irq_domain_ops its_domain_ops = {.alloc = its_irq_domain_alloc,.free = its_irq_domain_free,.activate = its_irq_domain_activate,.deactivate = its_irq_domain_deactivate,};