中断管理基础学习笔记 - 5.2 ARM64高层中断处理

1. 前言

本专题我们开始学习进程管理部分。本文主要参考了《奔跑吧, Linux内核》、ULA、ULK的相关内容。
本专题记录ARM架构下中断是如何管理的,Linux内核中的中断管理机制是如何设计与实现的,以及常用的下半部机制,如软中断、tasklet、workqueue等。本文及后续中断相关笔记均以qemu 5.0.0内嵌平台为例,中断控制器采用GIC-400控制器,支持GIC version2技术规范。本文开始介绍ARM64的中断处理过程,上一节介绍了底层硬件中断的处理过程,其中会调用到handle_arch_irq,在中断管理基础学习笔记 - 2.中断控制器初始化一节的gic_of_init->set_handle_irq中介绍过,会将handle_arch_irq初始化为gic_handle_irq,这个就作为中断的顶层处理函数,本节会详细说明gic_handle_irq函数的处理流程。

kernel版本:5.10
平台:arm64

注:
为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|- -", 三级为”|- - -“

2. gic_handle_irq

<drivers/irqchip/irq-gic.c>
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
    |--struct gic_chip_data *gic = &gic_data[0];
    |--void __iomem *cpu_base = gic_data_cpu_base(gic);
    |--do {
           //读取GIC的GICC_IAR寄存器,读取行为本身是对中断的ack
           irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
           //获取发生中断的硬中断号
           irqnr = irqstat & GICC_IAR_INT_ID_MASK;
           if (unlikely(irqnr >= 1020))
               break;
           //写入GICC_EOIR寄存器,通知CPU interface中断处理完成
           if (static_branch_likely(&supports_deactivate_key))
               writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
           isb();
           //对SGI私有中断的处理
           if (irqnr <= 15)
               smp_rmb();
               this_cpu_write(sgi_intid, irqstat);
           handle_domain_irq(gic->domain, irqnr, regs);
       } while (1);

中断高层处理,这里有个疑问while循环是如何退出的?难道没有中断的时候GICC_IAR的bit0~bit9全1?

  1. readl_relaxed(cpu_base + GIC_CPU_INTACK);:读取GIC的GICC_IAR寄存器,读取行为本身是对中断的ack,根据中断管理基础学习笔记 - 1.概述一节中介绍的gic状态机,读取GICC_IAR寄存器就是对中断的ACK,会让中断从pending状态进入到active状态;

  2. writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI):写入GICC_EOIR寄存器,通知CPU interface中断处理完成,此时就是deactive 中断,让中断从active状态转到inactive状态;

  3. handle_domain_irq:前面介绍中断控制器初始化一文,对于gic中断控制器来讲会执行gic_of_init初始化,他会创建并注册irq_domain,此处的gic->domain就是gic初始化时创建的,它代表了中断控制器;在创建软硬中断号映射一文中,通过遍历dts中中断节点,为每个硬中断号创建了映射,并将映射关系保存在irq_domain->linear_revmap数组或revmap_tree树中,其中以硬中断号为索引。此处参数irqnr就是硬中断号,通过它可以知道软中断号,以软中断号为索引可以获取到irq_desc,进一步获取到irq_data,并获取到irqaction进行处理。

handle_domain_irq(struct irq_domain *domain,unsigned int hwirq, struct pt_regs *regs)
    |--__handle_domain_irq(domain, hwirq, true, regs);
           |--unsigned int irq = hwirq;
           |--irq_enter();
           |  //以硬中断号为索引软中断号返回给irq
           |--irq = irq_find_mapping(domain, hwirq);
           |--generic_handle_irq(irq);
           |--irq_exit();
  1. irq_enter:显示的告诉Linux内核现在进入中断上下文,主要通过增加preempt.count的HARDIRQ域计数值来实现;

  2. irq_find_mapping:以硬中断号为索引软中断号返回给irq,这里rq_domian会维护软硬中断号的映射关系,通过irq_domain可以获取到软中断号,其中如果hwirq < domain->revmap_size,则会直接通过domain->linear_revmap[hwirq]返回,否则以hwirq为索引通过domain->revmap_tree查找获得irq_data,irq_data->irq中就保存了对应的软中断号;

  3. generic_handle_irq:为通用中断处理的主函数

  4. irq_exit:与irq_enter相匹配进行递减,并处理软中断

|- -irq_enter

void irq_enter(void)//Enter an interrupt context including RCU update
    |--rcu_irq_enter();
    |--irq_enter_rcu();
           |--if (is_idle_task(current) && !in_interrupt())
           |     local_bh_disable();
           |      tick_irq_enter();
           |      _local_bh_enable();
           |--__irq_enter();
                 |--account_irq_enter_time(current);
                 |--preempt_count_add(HARDIRQ_OFFSET);
                 |      |-- __preempt_count_add(val);
                 |              |--u32 pc = READ_ONCE(current_thread_info()->preempt.count);
                 |              |--pc += val;
                 |              |--WRITE_ONCE(current_thread_info()->preempt.count, pc);
                 |--lockdep_hardirq_enter();

从上面可以看出irq_enter->preempt_count_add->__preempt_count_add主要通过操作了current_thread_info()->preempt.count变量,此变量主要用于计数操作,由参数HARDIRQ_OFFSET可知操作的是硬件中断计数器,它占用4个bit,此计数值非0表示当前处于硬件中断上下文处理,此处是累加了preempt.count的HARDIRQ域的计数值,表示当前处于硬件中断上下文,同样在irq_exit时需要匹配递减。

/*
 * We put the hardirq and softirq counter into the preemption
 * counter. The bitmask has the following meaning:
 *
 * - bits 0-7 are the preemption count (max preemption depth: 256)
 * - bits 8-15 are the softirq count (max # of softirqs: 256)
 *
 * The hardirq count could in theory be the same as the number of
 * interrupts in the system, but we run all interrupt handlers with
 * interrupts disabled, so we cannot have nesting interrupts. Though
 * there are a few palaeontologic drivers which reenable interrupts in
 * the handler, so we need more than one bit here.
 *
 *         PREEMPT_MASK:        0x000000ff
 *         SOFTIRQ_MASK:        0x0000ff00
 *         HARDIRQ_MASK:        0x000f0000
 *             NMI_MASK:        0x00f00000
 * PREEMPT_NEED_RESCHED:        0x80000000
 */
/*
 *
#define PREEMPT_BITS    8
#define SOFTIRQ_BITS    8
#define HARDIRQ_BITS    4
#define NMI_BITS        4

#define PREEMPT_SHIFT   0
#define SOFTIRQ_SHIFT   (PREEMPT_SHIFT + PREEMPT_BITS)
#define HARDIRQ_SHIFT   (SOFTIRQ_SHIFT + SOFTIRQ_BITS)
#define NMI_SHIFT       (HARDIRQ_SHIFT + HARDIRQ_BITS)

#define __IRQ_MASK(x)   ((1UL << (x))-1)

#define PREEMPT_MASK    (__IRQ_MASK(PREEMPT_BITS) << PREEMPT_SHIFT)
#define SOFTIRQ_MASK    (__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT)
#define HARDIRQ_MASK    (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT)
#define NMI_MASK        (__IRQ_MASK(NMI_BITS)     << NMI_SHIFT)

#define PREEMPT_OFFSET  (1UL << PREEMPT_SHIFT)
#define SOFTIRQ_OFFSET  (1UL << SOFTIRQ_SHIFT)
#define HARDIRQ_OFFSET  (1UL << HARDIRQ_SHIFT)
#define NMI_OFFSET      (1UL << NMI_SHIFT)

通过如上可知:current_thread_info()->preempt.count变量的计数值按如下进行划分:
在这里插入图片描述

|- -generic_handle_irq

int generic_handle_irq(unsigned int irq)
    |--struct irq_desc *desc = irq_to_desc(irq);
    |--generic_handle_irq_desc(desc);
    	|--desc->handle_irq(desc)
           |--handle_fasteoi_irq(desc)
                  |--handle_irq_event(desc);
                         |--__handle_irq_event_percpu(desc, &flags);
                                |--for_each_action_of_desc(desc, action)
                                       |  //标记中断被强制线程化
                                       |--if (irq_settings_can_thread(desc) &&
                                       |           !(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
                                       |       lockdep_hardirq_threaded();
                                       |--res = action->handler(irq, action->dev_id);   
                                       |--switch (res)
                                          //primary handler处理完毕,需要唤醒中断线程
                                          case IRQ_WAKE_THREAD:
                                              __irq_wake_thread(desc, action);
                                          //无中断线程
                                          case IRQ_HANDLED:
                                              *flags |= action->flags;   

generic_handle_irq为通用中断处理

  1. desc->handle_irq(desc): 我们在中断管理基础学习笔记 - 3. 创建软硬中断号映射一文的irq_domain_associate中初始化desc->handle_irq为handle_fasteoi_irq,那里我们提到了与handle_arch_irq是何种关系?此处可以做出回答,handle_arch_irq可以理解为中断从底层处理迈入顶层处理的入口,而desc->handle_irq为中断的回调,handle_arch_irq通过层层跋涉最终会调用到desc->handle_irq,并最终调用irqaction->handler,此处desc->handle_irq为handle_fasteoi_irq。

  2. action->handler:此处就是注册中断一文中介绍的,request_threaded_irq会将参数handler传递给action->handler,此函数如果为空,将采用默认的irq_default_primary_handler,他将返回IRQ_WAKE_THREAD,唤醒中断线程执行,中断线程在注册中断时创建;如果primary handler返回的是IRQ_HANDLED,则表示没有中断线程

|- -irq_exit

void irq_exit(void)
    |--__irq_exit_rcu();
    |      |--local_irq_disable();
    |      |--account_irq_exit_time(current);
    |      |  //与irq_enter的对应域匹配,递减
    |      |--preempt_count_sub(HARDIRQ_OFFSET);
    |      |  //退出中断上下文并且当前有软中断pending
    |      |--if (!in_interrupt() && local_softirq_pending())
    |      |      //对软中断进行处理
    |      |      invoke_softirq();
    |      |--tick_irq_exit();
    |--rcu_irq_exit();
    |--lockdep_hardirq_exit();
  1. local_softirq_pending:主要用来判断当前是否有软中断pending

  2. invoke_softirq:对软中断进行处理,后面会详细说明

|- - -local_softirq_pending

#define local_softirq_pending() (__this_cpu_read(local_softirq_pending_ref))

local_softirq_pending主要用来判断是否有软中断pending,其中local_softirq_pending_ref的定义如下:

#ifndef local_softirq_pending_ref
#define local_softirq_pending_ref irq_stat.__softirq_pending 
#endif

这里irq_stat的定义如下:

#ifndef __ARCH_IRQ_STAT 
DEFINE_PER_CPU_ALIGNED(irq_cpustat_t, irq_stat);
EXPORT_PER_CPU_SYMBOL(irq_stat)
#endif

irq_cpustat_t的定义如下:

typedef struct {
        unsigned int __softirq_pending;
} ____cacheline_aligned irq_cpustat_t;

由此可见local_softirq_pending就是判断__softirq_pending的值是否为0,如果不为0表示软中断pending,__softirq_pending的不同bit位表示不同的软中断,在触发软中断时会设置。下一节介绍软中断时再来详细说明

参考文档

奔跑吧,Linux内核

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值