• 深度解析Linux内核—中断


    中断

    中断是外围设备通知处理器的一种机制。

    1. 中断控制器

    外围设备不是把中断请求直接发送给处理器,而是发给中断控制器,由中断控制器转发给处理器。

    不同种类的中断控制器的访问方法存在差异,为了屏蔽差异,内核定义了中断控制器描述符irq_chip,每种中断控制器自定义各种操作函数。GIC v2控制器的描述符如下:

    1. drivers/irqchip/irq-gic.c
    2. tatic const struct irq_chip gic_chip = {
    3. .irq_mask = gic_mask_irq,
    4. .irq_unmask = gic_unmask_irq,
    5. .irq_eoi = gic_eoi_irq,
    6. .irq_set_type = gic_set_type,
    7. .irq_get_irqchip_state = gic_irq_get_irqchip_state,
    8. .irq_set_irqchip_state = gic_irq_set_irqchip_state,
    9. .flags = IRQCHIP_SET_TYPE_MASKED |
    10. IRQCHIP_SKIP_SET_WAKE |
    11. IRQCHIP_MASK_ON_SUSPEND,
    12. };

    2. 中断域

    一个大型系统可能有多个中断控制器,这些中断控制器可以级联,一个中断控制器作为中断源连接到另一个中断控制器,但只有一个中断控制器作为根控制器直接连接到处理器。为了把每个中断控制器本地的硬件中断映射到全局唯一的Linux中断号(也称为虚拟中断),内核定义了中断域irq_domain,每个中断控制器由自己的中断域。

    2.1. 创建中断域

    中断控制器的驱动程序使用分配函数irq_domain_add_*()创建和注册中断域。

    2.2. 创建映射

    创建中断域以后,需要向中断域添加硬件中断号到Linux中断号的映射,内核提供了函数irq_create_mapping:

    unsigned int irq_create_mapping(struct irq_domain *host, irq_hw_number_t hwirq);

    输入参数是中断域和硬件中断号,返回Linux中断号。

    该函数首先分配Linux中断号,然后把硬件中断号到Linux中断号的映射添加到中断域。

    2.3. 查找映射

    中断处理程序需要根据硬件中断号查找Linux中断号,内核提供了函数irq_find_mapping:

    unsigned int irq_find_mapping(struct irq_domain *host, irq_hw_number_t hwirq);

    输入参数是中断域和硬件中断号,返回Linux中断号。

    【文章福利】小编推荐自己的Linux内核技术交流群:【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)

    内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

    学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

    3. 中断控制器驱动初始化

    3.1. 设备树源文件

    ARM64架构使用扁平设备树(Flattened Device Tree,FDT)描述板卡的硬件信息,好处是可以把板卡的特定的代码从内核中删除,编译生成通用的板卡无关的内核。

    设备树源文件是文本文件,扩展名是“.dts”,需要在设备树源文件中描述中断的相关信息:

    (1)中断控制器的信息

    (2)对于作为中断源的外围设备,需要描述设备连接到哪个中断控制器,使用哪个硬件中断号

    3.2. 中断控制器匹配表

    在GIC v2控制器的驱动程序中,定义了多个类型为of_device_id的静态变量,成员compatible是驱动程序支持的设备的名称,成员data是初始化函数,编译器把这些静态变量放在专用的节“__irqchip_of_table”里面。

    我们把节“__irqchip_of_table”称为中断控制器匹配表,里面每个表项的格式是结构体of_device_id。

    1. drivers/irqchip/irq-gic.c
    2. IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
    3. ...
    4. IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
    5. IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
    6. ...

    把宏IRQCHIP_DECLARE展开以后是:

    1. static const struct of_device_id __of_table_cortex_gic_400
    2. __section(__irqchip_of_table)
    3. = { .compatible = "arm,gic-g400",
    4. .data = gic_of_init }
    5. ...
    6. static const struct of_device_id __of_table_cortex_a15_gic
    7. __section(__irqchip_of_table)
    8. = { .compatible = "arm,cortex-a15-gic",
    9. .data = gic_of_init }
    10. static const struct of_device_id __of_table_cortex_a9_gic
    11. __section(__irqchip_of_table)
    12. = { .compatible = "arm,cortex-a9-gic",
    13. .data = gic_of_init }
    14. ...

    3.3. 初始化

    在内核初始化的时候,匹配设备树文件中的中断控制器的属性“compatible”和内核的中断控制器匹配表,找到合适的中断控制器驱动程序,执行驱动程序的初始化函数。

    1. start_kernel() -> init_IRQ() -> irqchip_init()
    2. drivers/irqchip/irqchip.c
    3. void __init irqchip_init(void)
    4. {
    5. of_irq_init(__irqchip_of_table); // 参数是中断控制器匹配表的起始地址__irqchip_of_table
    6. ...
    7. }

    (1)函数of_irq_init

    1. driver/of/irq.c
    2. /**
    3. * of_irq_init - Scan and init matching interrupt controllers in DT
    4. * @matches: 0 terminated array of nodes to match and init function to call
    5. *
    6. * This function scans the device tree for matching interrupt controller nodes,
    7. * and calls their initialization functions in order with parents first.
    8. */
    9. void __init of_irq_init(const struct of_device_id *matches)
    10. {
    11. const struct of_device_id *match;
    12. struct device_node *np, *parent = NULL;
    13. struct of_intc_desc *desc, *temp_desc;
    14. struct list_head intc_desc_list, intc_parent_list;
    15. INIT_LIST_HEAD(&intc_desc_list);
    16. INIT_LIST_HEAD(&intc_parent_list);
    17. for_each_matching_node_and_match(np, matches, &match) { /* 遍历设备树文件的设备节点。如果属性compatible和中断控制器匹配表中的任何一条表项的字段compatible匹配,处理如下 */
    18. if (!of_property_read_bool(np, "interrupt-controller") ||
    19. !of_device_is_available(np)) /* 如果没有节点属性interrupt-controller,说明设备不是中断控制器,忽略该设备 */
    20. continue;
    21. if (WARN(!match->data, "of_irq_init: no init function for %s\n",
    22. match->compatible))
    23. continue;
    24. /*
    25. * Here, we allocate and populate an of_intc_desc with the node
    26. * pointer, interrupt-parent device_node etc.
    27. */
    28. desc = kzalloc(sizeof(*desc), GFP_KERNEL); /* 分配一个of_intc_desc实例 */
    29. if (WARN_ON(!desc)) {
    30. of_node_put(np);
    31. goto err;
    32. }
    33. desc->irq_init_cb = match->data; /* 成员irq_init_cb保存初始化函数 */
    34. desc->dev = of_node_get(np); /* 成员dev保存本设备的device_node */
    35. desc->interrupt_parent = of_irq_find_parent(np); /* 成员interrupt保存父设备 */
    36. if (desc->interrupt_parent == np)
    37. desc->interrupt_parent = NULL;
    38. list_add_tail(&desc->list, &intc_desc_list); /*of_intc_desc实例添加到链表intc_desc_list中 */
    39. }
    40. /*
    41. * The root irq controller is the one without an interrupt-parent.
    42. * That one goes first, followed by the controllers that reference it,
    43. * followed by the ones that reference the 2nd level controllers, etc.
    44. */
    45. while (!list_empty(&intc_desc_list)) { /* 遍历链表intc_desc_list,从根设备开始,先执行父设备的初始化函数,然后执行子设备的初始化函数 */
    46. /*
    47. * Process all controllers with the current 'parent'.
    48. * First pass will be looking for NULL as the parent.
    49. * The assumption is that NULL parent means a root controller.
    50. */
    51. list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
    52. int ret;
    53. if (desc->interrupt_parent != parent)
    54. continue;
    55. list_del(&desc->list);
    56. of_node_set_flag(desc->dev, OF_POPULATED);
    57. pr_debug("of_irq_init: init %pOF (%p), parent %p\n",
    58. desc->dev,
    59. desc->dev, desc->interrupt_parent);
    60. ret = desc->irq_init_cb(desc->dev,
    61. desc->interrupt_parent);
    62. if (ret) {
    63. of_node_clear_flag(desc->dev, OF_POPULATED);
    64. kfree(desc);
    65. continue;
    66. }
    67. /*
    68. * This one is now set up; add it to the parent list so
    69. * its children can get processed in a subsequent pass.
    70. */
    71. list_add_tail(&desc->list, &intc_parent_list);
    72. }
    73. /* Get the next pending parent that might have children */
    74. desc = list_first_entry_or_null(&intc_parent_list,
    75. typeof(*desc), list);
    76. if (!desc) {
    77. pr_err("of_irq_init: children remain, but no parents\n");
    78. break;
    79. }
    80. list_del(&desc->list);
    81. parent = desc->dev;
    82. kfree(desc);
    83. }
    84. list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
    85. list_del(&desc->list);
    86. kfree(desc);
    87. }
    88. err:
    89. list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
    90. list_del(&desc->list);
    91. of_node_put(desc->dev);
    92. kfree(desc);
    93. }
    94. }

    设备树文件“arch/arm64/boot/dts/arm/foundation-v8.dts”里面中断控制器的属性“compatible”是:

    compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic";

    和中断控制器匹配表中的

    1. { .compatible = "arm,cortex-a15-gic", .data = gic_of_init }
    2. { .compatible = "arm,cortex-a9-gic", .data = gic_of_init }

    匹配。

    (2)gic_of_init

    1. int __init
    2. gic_of_init(struct device_node *node, struct device_node *parent) /* 参数node是本中断控制器,参数parent是父设备 */
    3. {
    4. struct gic_chip_data *gic;
    5. int irq, ret;
    6. if (WARN_ON(!node))
    7. return -ENODEV;
    8. if (WARN_ON(gic_cnt >= CONFIG_ARM_GIC_MAX_NR))
    9. return -EINVAL;
    10. gic = &gic_data[gic_cnt]; /* 从全局数组gic_data取一个空闲的元素来保存本中断控制器的信息 */
    11. ret = gic_of_setup(gic, node); /* 调用函数gic_of_setup:从设备树文件读取中断控制器的属性reg,获取分发器和处理器接口的寄存器的物理地址范围,把物理地址映射到内核的虚拟地址空间 */
    12. if (ret)
    13. return ret;
    14. /*
    15. * Disable split EOI/Deactivate if either HYP is not available
    16. * or the CPU interface is too small.
    17. */
    18. if (gic_cnt == 0 && !gic_check_eoimode(node, &gic->raw_cpu_base))
    19. static_branch_disable(&supports_deactivate_key);
    20. ret = __gic_init_bases(gic, -1, &node->fwnode); /* 调用函数__gic_init_bases以初始化结构体gic_chip_data */
    21. if (ret) {
    22. gic_teardown(gic);
    23. return ret;
    24. }
    25. if (!gic_cnt) {
    26. gic_init_physaddr(node);
    27. gic_of_setup_kvm_info(node);
    28. }
    29. if (parent) { /* 如果本中断控制器有父设备,即作为中断源连接到其他中断控制器,处理如下 */
    30. irq = irq_of_parse_and_map(node, 0); /* 调用函数irq_of_parse_and_map:从设备树文件中本设备节点的属性interrupts获取硬件中断号,把硬件中断号映射到Linux中断号n */
    31. gic_cascade_irq(gic_cnt, irq); /* 调用函数gic_cascade_irq:把Linux中断号n中断描述符的成员handle_irq()设置为函数gic_handle_cascade_irq() */
    32. }
    33. if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
    34. gicv2m_init(&node->fwnode, gic_data[gic_cnt].domain);
    35. gic_cnt++;
    36. return 0;
    37. }

    (3)函数__gic_init_bases

    1. static int __init __gic_init_bases(struct gic_chip_data *gic,
    2. int irq_start,
    3. struct fwnode_handle *handle)
    4. {
    5. char *name;
    6. int i, ret;
    7. if (WARN_ON(!gic || gic->domain))
    8. return -EINVAL;
    9. if (gic == &gic_data[0]) { /* 如果本中断控制器是根控制器,处理如下: */
    10. /*
    11. * Initialize the CPU interface map to all CPUs.
    12. * It will be refined as each CPU probes its ID.
    13. * This is only necessary for the primary GIC.
    14. */
    15. for (i = 0; i < NR_GIC_CPU_IF; i++)
    16. gic_cpu_map[i] = 0xff;
    17. #ifdef CONFIG_SMP
    18. set_smp_cross_call(gic_raise_softirq); /* 把全局函数指针__smp_cross_call设置为函数gic_raise_softirq */
    19. #endif /* 用来发送软件生成的中断,即一个处理器向其他处理器发送中断 */
    20. cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
    21. "irqchip/arm/gic:starting",
    22. gic_starting_cpu, NULL);
    23. set_handle_irq(gic_handle_irq); /* 把全局函数指针handle_arch_irq设置为函数gic_handle_irq,该函数是中断处理程序C语言部分的入口 */
    24. if (static_branch_likely(&supports_deactivate_key))
    25. pr_info("GIC: Using split EOI/Deactivate mode\n");
    26. }
    27. /* 调用函数gic_init_chip以初始化中断控制器描述符irq_chip */
    28. if (static_branch_likely(&supports_deactivate_key) && gic == &gic_data[0]) {
    29. name = kasprintf(GFP_KERNEL, "GICv2");
    30. gic_init_chip(gic, NULL, name, true);
    31. } else {
    32. name = kasprintf(GFP_KERNEL, "GIC-%d", (int)(gic-&gic_data[0]));
    33. gic_init_chip(gic, NULL, name, false);
    34. }
    35. ret = gic_init_bases(gic, irq_start, handle); /* 调用函数gic_init_bases进行初始化:为本中断控制器分配中断域,初始化中断控制器的分发器的各种寄存器,初始化中断控制器的处理器接口的各种寄存器 */
    36. if (ret)
    37. kfree(name);
    38. return ret;
    39. }

    4. Linux中断处理

    对于中断控制器的每个中断源,向中断域添加硬件中断号到Linux中断号的映射时,内核分配一个Linux中断号和一个中断描述符irq_desc,中断描述符由两个层次的中断处理函数:

    (1)第一层处理函数是中断描述符的成员handle_irq()

    (2)第二层处理函数是设备驱动程序注册的处理函数。中断描述符由一个中断处理链表(irq_desc.action),每个中断处理描述符(irq_action)保存设备驱动程序注册的处理函数。因为多个设备可以共享同一个硬件中断号,所以中断处理链表可能挂载多个中断处理描述符。

    怎么存储Linux中断号到中断描述符的映射关系?

    有两种实现方式:

    (1)如果中断编号是稀疏的(即不连续),那么使用基数树(radix tree)存储。需要开启配置宏CONFIG_SPARSE_IRQ。

    (2)如果中断编号是连续的,那么使用数组存储。

    1. #ifdef CONFIG_SPARSE_IRQ
    2. ...
    3. static RADIX_TREE(irq_desc_tree, GFP_KERNEL);
    4. ...
    5. #else /* !CONFIG_SPARSE_IRQ */
    6. struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    7. [0 ... NR_IRQS-1] = {
    8. .handle_irq = handle_bad_irq,
    9. .depth = 1,
    10. .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    11. }
    12. };
    13. ...
    14. #endif /* !CONFIG_SPARSE_IRQ */

    把硬件中断号映射到Linux中断号的时候,根据硬件中断的类型设置中断描述符的成员handle_irq(),以GIC v2控制器为例,函数gic_irq_domain_map所做的处理如下:

    1. irq_create_mapping -> irq_domain_associate() -> domain->ops->map() -> gic_irq_domain_map()
    2. drivers/irqchip/irq-gic.c
    3. tatic int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
    4. irq_hw_number_t hw)
    5. {
    6. struct gic_chip_data *gic = d->host_data;
    7. if (hw < 32) { /* 如果硬件中断号小于32,说明是软件生成的中断或私有外设中断,那么把终端描述符的成员handle_irq()设置为函数handle_percpu_devid_irq */
    8. irq_set_percpu_devid(irq);
    9. irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
    10. handle_percpu_devid_irq, NULL, NULL);
    11. irq_set_status_flags(irq, IRQ_NOAUTOEN);
    12. } else { /* 如果硬件中断号大于或等于32,说明共享外设中断,那么把中断描述符的成员handle_irq()设置为函数handle_fasteoi_irq */
    13. irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
    14. handle_fasteoi_irq, NULL, NULL);
    15. irq_set_probe(irq);
    16. irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
    17. }
    18. return 0;
    19. }

    在ARM64架构下,在异常级别1的异常向量表中,中断的入口有3个:

    (1)如果处理器处在内核模式(异常级别1),中断的入口是el1_irq;

    (2)如果处理器正在用户模式(异常级别0)下执行64位应用程序,中断的入口是el0_irq;

    (3)如果处理器正在用户模式(异常级别0)下执行32位应用程序,中断的入口是el0_irq_compat。

    假设处理器正在用户模式(异常级别0)下执行64位应用程序,中断控制器是GIC v2控制器,Linux中断处理流程如下:

    函数el0_irq的代码如下:

    1. arch/arm64/kernel/irq.c
    2. /* 每个处理器有一个专用的中断栈 */
    3. DEFINE_PER_CPU(unsigned long *, irq_stack_ptr);
    4. arch/arm64/kernel/entry.S
    5. /*
    6. * Interrupt handling.
    7. */
    8. .macro irq_handler
    9. ldr_l x1, handle_arch_irq
    10. mov x0, sp
    11. irq_stack_entry // 从进程的内核栈切换到中断栈
    12. blr x1 // 调用函数指针handle_arch_irq指向的函数
    13. irq_stack_exit // 从中断栈切换到进程的内核栈
    14. .endm
    15. .align 6
    16. el1_irq:
    17. kernel_entry 1
    18. enable_da_f
    19. #ifdef CONFIG_TRACE_IRQFLAGS
    20. bl trace_hardirqs_off
    21. #endif
    22. irq_handler // irq_handler是一个宏
    23. #ifdef CONFIG_PREEMPT
    24. ldr x24, [tsk, #TSK_TI_PREEMPT] // get preempt count 读取抢占计数
    25. cbnz x24, 1f // preempt count != 0 抢占计数不等于0
    26. bl el1_preempt
    27. 1:
    28. #endif
    29. #ifdef CONFIG_TRACE_IRQFLAGS
    30. bl trace_hardirqs_on
    31. #endif
    32. kernel_exit 1
    33. ENDPROC(el1_irq)

    在gic_of_init() -> __gic_init_bases中初始化了函数handle_arch_irq,GIC v2控制器把该函数指针设置为函数gic_handle_irq。

    1. drivers/irqchip/irq-gic.c
    2. static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
    3. {
    4. u32 irqstat, irqnr;
    5. struct gic_chip_data *gic = &gic_data[0];
    6. void __iomem *cpu_base = gic_data_cpu_base(gic);
    7. do {
    8. irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); /* 读取处理器接口的中断确认寄存器得到中断号 */
    9. irqnr = irqstat & GICC_IAR_INT_ID_MASK;
    10. if (likely(irqnr > 15 && irqnr < 1020)) { /* 如果硬件中断号大于15且小于1020,即中断是由外围设备发送的 */
    11. if (static_branch_likely(&supports_deactivate_key)) /* 把中断号写到处理器接口的中断结束寄存器中,指示中断处理完成 */
    12. writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
    13. isb();
    14. handle_domain_irq(gic->domain, irqnr, regs); /* 如果是私有外设中断,那么中断描述符的成员handle_irq()是函数handle_percpu_devid_irq; */
    15. continue; /* 如果是共享外设中断,那么中断描述符的成员handle_irq()是函数handle_fasteoi_irq */
    16. }
    17. if (irqnr < 16) { /* 如果硬件中断号小于16,即软件生成的中断 */
    18. writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
    19. if (static_branch_likely(&supports_deactivate_key)) /* 把中断号写到处理器接口的中断结束寄存器中,指示中断处理完成 */
    20. writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
    21. #ifdef CONFIG_SMP
    22. /*
    23. * Ensure any shared data written by the CPU sending
    24. * the IPI is read after we've read the ACK register
    25. * on the GIC.
    26. *
    27. * Pairs with the write barrier in gic_raise_softirq
    28. */
    29. smp_rmb();
    30. handle_IPI(irqnr, regs);
    31. #endif
    32. continue;
    33. }
    34. break;
    35. } while (1);
    36. }

    函数handle_domain_irq():

    1. include/linux/irqdesc.h
    2. static inline void generic_handle_irq_desc(struct irq_desc *desc)
    3. {
    4. desc->handle_irq(desc);
    5. }
    6. kernel/irq/irqdesc.c
    7. int generic_handle_irq(unsigned int irq)
    8. {
    9. struct irq_desc *desc = irq_to_desc(irq);
    10. if (!desc)
    11. return -EINVAL;
    12. generic_handle_irq_desc(desc);
    13. return 0;
    14. }
    15. kernel/irq/irqdesc.c
    16. #ifdef CONFIG_HANDLE_DOMAIN_IRQ
    17. int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
    18. bool lookup, struct pt_regs *regs)
    19. {
    20. struct pt_regs *old_regs = set_irq_regs(regs);
    21. unsigned int irq = hwirq;
    22. int ret = 0;
    23. irq_enter();
    24. #ifdef CONFIG_IRQ_DOMAIN
    25. if (lookup)
    26. irq = irq_find_mapping(domain, hwirq);
    27. #endif
    28. /*
    29. * Some hardware gives randomly wrong interrupts. Rather
    30. * than crashing, do something sensible.
    31. */
    32. if (unlikely(!irq || irq >= nr_irqs)) {
    33. ack_bad_irq(irq);
    34. ret = -EINVAL;
    35. } else {
    36. generic_handle_irq(irq);
    37. }
    38. irq_exit();
    39. set_irq_regs(old_regs);
    40. return ret;
    41. }
    42. #endif
    43. include/linux/irqdesc.h
    44. static inline int handle_domain_irq(struct irq_domain *domain,
    45. unsigned int hwirq, struct pt_regs *regs)
    46. {
    47. return __handle_domain_irq(domain, hwirq, true, regs);
    48. }

    如果是私有外设中断,那么中断描述符的成员handle_irq()是函数handle_percpu_devid_irq,其代码如下:

    1. kernel/irq/chip.c
    2. void handle_percpu_devid_irq(struct irq_desc *desc)
    3. {
    4. struct irq_chip *chip = irq_desc_get_chip(desc);
    5. struct irqaction *action = desc->action;
    6. unsigned int irq = irq_desc_get_irq(desc);
    7. irqreturn_t res;
    8. ...
    9. if (chip->irq_ack)
    10. chip->irq_ack(&desc->irq_data);
    11. if (likely(action)) {
    12. ...
    13. res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));
    14. ...
    15. } else {
    16. ...
    17. }
    18. if (chip->irq_eoi)
    19. chip->irq_eoi(&desc->irq_data);
    20. }

    如果是共享外设中断,那么中断描述符的成员handle_irq()是函数handle_fasteoi_irq,其代码如下:

    1. kernel/irq/chip.c
    2. void handle_fasteoi_irq(struct irq_desc *desc)
    3. {
    4. struct irq_chip *chip = desc->irq_data.chip;
    5. raw_spin_lock(&desc->lock);
    6. if (!irq_may_run(desc))
    7. goto out;
    8. desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
    9. /*
    10. * If its disabled or no action available
    11. * then mask it and get out of here:
    12. */
    13. if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
    14. desc->istate |= IRQS_PENDING;
    15. mask_irq(desc);
    16. goto out;
    17. }
    18. kstat_incr_irqs_this_cpu(desc);
    19. if (desc->istate & IRQS_ONESHOT)
    20. mask_irq(desc);
    21. preflow_handler(desc);
    22. handle_irq_event(desc); /* 调用函数handle_irq_event执行设备驱动程序注册的处理函数 */
    23. cond_unmask_eoi_irq(desc, chip);
    24. raw_spin_unlock(&desc->lock);
    25. return;
    26. out:
    27. if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
    28. chip->irq_eoi(&desc->irq_data);
    29. raw_spin_unlock(&desc->lock);
    30. }

    函数handle_irq_event把主要工作委托给函数__handle_irq_event_percpu。函数__handle_irq_event_percpu遍历中断描述符的中断处理链表,执行每个中断处理描述符的处理函数,其代码如下:

    1. handle_irq_event() -> handle_irq_event_percpu() -> __handle_irq_event_percpu()
    2. kernel/irq/handle.c
    3. irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
    4. {
    5. irqreturn_t retval = IRQ_NONE;
    6. unsigned int irq = desc->irq_data.irq;
    7. struct irqaction *action;
    8. /* 遍历中断描述符的的中断处理链表,执行每个中断处理描述符的处理函数 */
    9. record_irq_time(desc);
    10. for_each_action_of_desc(desc, action) {
    11. irqreturn_t res;
    12. trace_irq_handler_entry(irq, action);
    13. res = action->handler(irq, action->dev_id);
    14. trace_irq_handler_exit(irq, action, res);
    15. if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
    16. irq, action->handler))
    17. local_irq_disable();
    18. switch (res) {
    19. case IRQ_WAKE_THREAD:
    20. /*
    21. * Catch drivers which return WAKE_THREAD but
    22. * did not set up a thread function
    23. */
    24. if (unlikely(!action->thread_fn)) {
    25. warn_no_thread(irq, action);
    26. break;
    27. }
    28. __irq_wake_thread(desc, action);
    29. /* 继续往下走,把action->flags作为生成随机数的一个因子 */
    30. /* Fall through to add to randomness */
    31. case IRQ_HANDLED:
    32. *flags |= action->flags;
    33. break;
    34. default:
    35. break;
    36. }
    37. retval |= res;
    38. }
    39. return retval;
    40. }

    5. 中断线程化

    中断线程化就是使用内核线程处理中断,目的是减少系统关中断的时间,增强系统的实时性。内核提供的函数request_threaded_irq()用来注册线程化的中断:

    1. int request_threaded_irq(unsigned int irq, irq_handler_t handler,
    2. irq_handler_t thread_fn, unsigned long irqflags,
    3. const char *devname, void *dev_id)

    参数thread_fn是线程处理函数。

    少数中断不能线程化,典型的例子是时钟中断,有些流氓进程不主动让出处理器,内核只能依靠周期性的时钟中断夺回处理器的控制权,时钟中断是调度器的脉搏。对于不能线程化的中断,注册处理函数的时候必须设置标志IRQF_NO_THREAD。

    如果开启了强制中断线程化的配置宏CONFIG_IRQ_FORCED_THREADING,并且在引导内核的时候指定内核参数“threadirqs”,那么强制除了标记IRQF_NO_THREAD以外的所有中断线程化。

    每个中断描述符(irqaction)对应一个内核线程,其代码如下:

    1. include/linux/interrupt.h
    2. struct irqaction {
    3. irq_handler_t handler;
    4. void *dev_id;
    5. void __percpu *percpu_dev_id;
    6. struct irqaction *next; /* 中断处理描述符链表 */
    7. irq_handler_t thread_fn; /* 指向线程处理函数 */
    8. struct task_struct *thread; /* 指向内核线程的进程描述符 */
    9. struct irqaction *secondary;
    10. unsigned int irq;
    11. unsigned int flags;
    12. unsigned long thread_flags;
    13. unsigned long thread_mask;
    14. const char *name;
    15. struct proc_dir_entry *dir;
    16. } ____cacheline_internodealigned_in_smp;

    1. request_threaded_irq() -> __setup_irq() -> setup_irq_thread()
    2. kernel/irq/manage.c
    3. static int
    4. setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
    5. {
    6. struct task_struct *t;
    7. struct sched_param param = {
    8. .sched_priority = MAX_USER_RT_PRIO/2, /* */
    9. };
    10. if (!secondary) {
    11. t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
    12. new->name); /* 名称是“irq/”后面跟着Linux中断号,线程处理函数是irq_thread() */
    13. } else {
    14. t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
    15. new->name);
    16. param.sched_priority -= 1;
    17. }
    18. if (IS_ERR(t))
    19. return PTR_ERR(t);
    20. sched_setscheduler_nocheck(t, SCHED_FIFO, &param); /* 中断处理线程是优先级为50、调度策略是SCHED_FIFO的实时内核线程 */
    21. /*
    22. * We keep the reference to the task struct even if
    23. * the thread dies to avoid that the interrupt code
    24. * references an already freed task_struct.
    25. */
    26. get_task_struct(t);
    27. new->thread = t;
    28. /*
    29. * Tell the thread to set its affinity. This is
    30. * important for shared interrupt handlers as we do
    31. * not invoke setup_affinity() for the secondary
    32. * handlers as everything is already set up. Even for
    33. * interrupts marked with IRQF_NO_BALANCE this is
    34. * correct as we want the thread to move to the cpu(s)
    35. * on which the requesting code placed the interrupt.
    36. */
    37. set_bit(IRQTF_AFFINITY, &new->thread_flags);
    38. return 0;
    39. }

    在中断处理程序中,函数__handle_irq_event_percpu遍历中断描述符的中断处理链表,执行每个中断处理描述符的处理函数。如果返回IRQ_WAKE_THREAD,说明是线程化的中断,那么唤醒中断处理线程。

    中断处理线程的处理函数是irq_thread(),调用函数irq_thread_fn(),然后函数irq_thread_fn()调用注册的线程处理函数。

    1. kernel/irq/manage.c
    2. static irqreturn_t irq_thread_fn(struct irq_desc *desc,
    3. struct irqaction *action)
    4. {
    5. irqreturn_t ret;
    6. ret = action->thread_fn(action->irq, action->dev_id);
    7. if (ret == IRQ_HANDLED)
    8. atomic_inc(&desc->threads_handled);
    9. irq_finalize_oneshot(desc, action);
    10. return ret;
    11. }
    12. static int irq_thread(void *data)
    13. {
    14. struct callback_head on_exit_work;
    15. struct irqaction *action = data;
    16. struct irq_desc *desc = irq_to_desc(action->irq);
    17. irqreturn_t (*handler_fn)(struct irq_desc *desc,
    18. struct irqaction *action);
    19. if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
    20. &action->thread_flags))
    21. handler_fn = irq_forced_thread_fn;
    22. else
    23. handler_fn = irq_thread_fn;
    24. init_task_work(&on_exit_work, irq_thread_dtor);
    25. task_work_add(current, &on_exit_work, false);
    26. irq_thread_check_affinity(desc, action);
    27. while (!irq_wait_for_interrupt(action)) {
    28. irqreturn_t action_ret;
    29. irq_thread_check_affinity(desc, action);
    30. action_ret = handler_fn(desc, action);
    31. if (action_ret == IRQ_WAKE_THREAD)
    32. irq_wake_secondary(desc, action);
    33. wake_threads_waitq(desc);
    34. }
    35. /*
    36. * This is the regular exit path. __free_irq() is stopping the
    37. * thread via kthread_stop() after calling
    38. * synchronize_hardirq(). So neither IRQTF_RUNTHREAD nor the
    39. * oneshot mask bit can be set.
    40. */
    41. task_work_cancel(current, irq_thread_dtor);
    42. return 0;
    43. }

    6. 禁止/开启中断

    禁止中断接口:

    (1)local_irq_disable()

    (2)local_irq_save(flags):首先把中断状态保存在参数flags中,然后禁止中断

    这两个接口只能禁止本处理器的中断,不能禁止其他处理器的中断。

    开启中断接口:

    (1)local_irq_enable()

    (2)local_irq_restore(flags):恢复本处理器的中断状态

    local_irq_disable()和local_irq_enable()不能嵌套使用,local_irq_save(flags)和local_irq_restore(flags)可以嵌套使用。

    7. 禁止/开启单个中断

    禁止中断的函数是:void disable_irq(unsigned int irq),参数irq是Linux中断号

    开启中断的函数是:void enable_irq(unsigned int irq),参数irq是Linux中断号

    8. 中断亲和性

    在多处理器系统中,管理员可以设置中断亲和性,允许中断控制器把某个中断转发给哪些处理器,有两种配置方法:

    (1)写文件“/proc/irq/IRQ#/smp_affinity”,参数是位掩码

    (2)写文件“/proc/irq/IRQ#/smp_affinity_list”,参数是处理器列表

    内核提供了设置中断亲和性的函数:

    int irq_set_affinity(unsigned int irq, const struct cpumask *cpumask)

    参数irq是Linux中断号,参数cpumask是处理器位掩码。

    9. 处理器间中断

    处理器间中断(Inter-Processor Interrupt,IPI)是一种特殊的中断,在多处理器系统中,一个处理器可以向其他处理器发送中断,要求目标处理器执行某件事情。

    常见的使用处理器间中断的函数如下:

    (1)在所有处理器上执行同一个函数:

    int up_smp_call_function(smp_call_func_t func, void *info, int wait);

    (2)在指定的处理器上执行一个函数

    int smp_call_function_single(int cpuid, smp_call_func_t func, void *info, int wait);

    (3)要求指定的处理器重新调度进程

    void smp_send_reschedule(int cpu);

    对于ARM64架构的GIC控制器,把处理器间生成的中断称为软件生成的中断。

    函数handle_IPI负责处理处理器减中断,参数ipinr是硬件中断号,其代码如下:

    1. arch/arm64/kernel/smp.c
    2. void handle_IPI(int ipinr, struct pt_regs *regs)
    3. {
    4. unsigned int cpu = smp_processor_id();
    5. struct pt_regs *old_regs = set_irq_regs(regs);
    6. if ((unsigned)ipinr < NR_IPI) {
    7. trace_ipi_entry_rcuidle(ipi_types[ipinr]);
    8. __inc_irq_stat(cpu, ipi_irqs[ipinr]);
    9. }
    10. /* 目前支持7种处理间中断 */
    11. switch (ipinr) {
    12. case IPI_RESCHEDULE: /* 硬件中断号是0,重新调度进程,函数smp_send_reschedule()生成的中断 */
    13. scheduler_ipi();
    14. break;
    15. case IPI_CALL_FUNC: /* 硬件中断号是1,执行函数,函数smp_call_function生成的中断 */
    16. irq_enter();
    17. generic_smp_call_function_interrupt();
    18. irq_exit();
    19. break;
    20. case IPI_CPU_STOP: /* 硬件中断号是2,使处理器停止,函数smp_send_stop()生成的中断 */
    21. irq_enter();
    22. ipi_cpu_stop(cpu);
    23. irq_exit();
    24. break;
    25. case IPI_CPU_CRASH_STOP: /* 硬件中断号是3,使处理器停止,函数smp_send_crash_stop()生成的中断 */
    26. if (IS_ENABLED(CONFIG_KEXEC_CORE)) {
    27. irq_enter();
    28. ipi_cpu_crash_stop(cpu, regs);
    29. unreachable();
    30. }
    31. break;
    32. #ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
    33. case IPI_TIMER: /* 硬件中断号是4,广播的时钟事件,函数tick_broadcast()生成的中断 */
    34. irq_enter();
    35. tick_receive_broadcast();
    36. irq_exit();
    37. break;
    38. #endif
    39. #ifdef CONFIG_IRQ_WORK
    40. case IPI_IRQ_WORK: /* 硬件中断号是5,在硬中断上下文中执行回调函数,函数irq_work_queue()生成的中断 */
    41. irq_enter();
    42. irq_work_run();
    43. irq_exit();
    44. break;
    45. #endif
    46. #ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL
    47. case IPI_WAKEUP: /* 硬件中断号是6,唤醒处理器,函数acpi_parking_protocol_cpu_boot()生成的中断 */
    48. WARN_ONCE(!acpi_parking_protocol_valid(cpu),
    49. "CPU%u: Wake-up IPI outside the ACPI parking protocol\n",
    50. cpu);
    51. break;
    52. #endif
    53. default:
    54. pr_crit("CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr);
    55. break;
    56. }
    57. if ((unsigned)ipinr < NR_IPI)
    58. trace_ipi_exit_rcuidle(ipi_types[ipinr]);
    59. set_irq_regs(old_regs);
    60. }

     

  • 相关阅读:
    只看优点,这2款可视化产品你更心水谁?
    lombok总结
    2022/11/18[指针] 关于指针的初步理解
    如何确保您的数据提供值得信赖的见解
    宏集案例 | eX707G人机界面在石油钻井工程中的应用
    使用Redis完成商品秒杀业务
    ​力扣解法汇总764. 最大加号标志
    【负荷预测】基于双向LSTM模型进行电力需求预测(Matlab代码实现)
    在Linux/Ubuntu/Debian中使用7z压缩和解压文件
    汽车以太网IOP测试新利器
  • 原文地址:https://blog.csdn.net/m0_74282605/article/details/128132781