【Linux】—中断处理流程

Linux中断的实现

简单了解Linux中实现中断过程。这里参考一篇博文、《深入Linux内核架构》和Linux 2.6.11源码,基于ARM架构实现的中断过程来理解中断的实现过程

  • 整个中断的流程,如下图所示(这也是本节的梳理脉络)
    在这里插入图片描述

数据结构

  • irq_desc
    在Linux内核中,irq_desc代表中断描述符。每个IRQ(中断请求)都有一个对应的irq_desc结构体,用于管理和处理该中断的相关信息。irq_desc结构体包含了许多重要的中断信息,如中断处理程序、中断掩码、中断状态等等
    struct irq_desc {
          irq_flow_handler_t handle_irq;
          struct irq_chip *chip;
          struct msi_desc *msi_desc;
          void *handler_data;
          void *chip_data;
          struct irqaction *action;/* IRQ操作列表 */
          unsigned int status;/* IRQ状态 */
          unsigned int depth;/* 嵌套停用IRQ */
          unsigned int wake_depth;/* nested wake enables */
          unsigned int irq_count;/* 用于检测错误的中断 */
          unsigned int irqs_unhandled;
          unsigned long last_unhandled;/* Aging timer for unhandled count */
          spinlock_t lock;
          ...
          const char *name;//产生中断的硬件名字
      };
    
    属性作用
    handle_irq中断处理函数。每当发生中断时,特定于体系结构的代码都会调用handle_irq
    chip用于底层的硬件访问。包含这个中断的清除、屏蔽、使能等底层函数
    handler_data可以指向任意数据。可以是特定于IRQ或处理程序的。
    actionaction链表,用于中断处理函数
    statusIRQ状态
  • irq_chip
    用于对中断控制器的操作
    struct irq_chip {
          const char *name;
          unsigned int (*startup)(unsigned int irq);
          void (*shutdown)(unsigned int irq);
          void (*enable)(unsigned int irq);
          void (*disable)(unsigned int irq);
          void (*ack)(unsigned int irq);
          ...
          void (*end)(unsigned int irq);
          void (*set_affinity)(unsigned int irq, cpumask_t dest);
          ...
    }
    
    属性作用
    startup启动IRQ。用于第一次初始化一个IRQ。
    shutdown关闭IRQ
    enable激活IRQ,它执行IRQ由禁用状态到启用状态的转换
    disable禁用IRQ
    ack与中断控制器的硬件密切相关。在某些模式中,IRQ请求的到达(以及在处理器的对应中断)必须显式确认,后续的请求才能进行处理
    end标记中断处理在电流层次的结束。如果一个中断在中断处理期间被禁用,那么该函数负责重新启用此类中断。
    set_affinity指定CPU来处理特定的IRQ。这使得可以将IRQ分配给某些CPU

|set_type|设置IRQ的电流类型。该方法主要使用在ARM、PowerPC和SuperH机器上。中断的触发类型,可以分为两类:边沿触发、电平触发|

  • irqaction
    用来存设备驱动注册的中断处理函数,一个中断可以有多个处理函数,当一个中断有多个处理函数,说明这个是共享中断。

    struct irqaction {
          irq_handler_t handler;
          unsigned long flags;
          cpumask_t mask;
          const char *name;
          void *dev_id;
          struct irqaction *next;
          int irq;
          struct proc_dir_entry *dir;
      };
    
    属性作用
    handler等于用户注册的中断处理函数,中断发生时就会运行这个中断处理函数(在设备请求一个系统中断,而中断控制器通过引发中断将该请求转发到处理器的时候,内核将调用该处理程序函数,和handle_irq之间存在区别,但目前我不清楚)
    flags标志变量,通过位图描述了IRQ(和相关的中断)的一些特性
    name用于标识硬件设备
    dev_id指向在所有内核数据结构中唯一标识了该设备的数据结构实例
    next实现共享的IRQ处理程序,几个irqaction实例聚集到一个链表中。链表的所有元素都必须处理同一IRQ编号
  • 通过图例来看irq_descirq_chipirqaction之间的关系
    在这里插入图片描述

中断处理流程

  • asm_do_IRQ
    对中断的处理流程
    asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs){
      struct pt_regs *old_regs = set_irq_regs(regs);//保存现场
      struct irq_desc *desc = irq_desc + irq;//获取对应的irq_desc
      if (irq >= NR_IRQS)
          desc = &bad_irq_desc;
      irq_enter();//对中断进行处理
      desc_handle_irq(irq, desc);
      /* AT91 specific workaround */
      irq_finish(irq);
      irq_exit();//退出中断处理程序
      set_irq_regs(old_regs);//恢复现场
      }
    
    结合上面梳理的数据结构,可以将中断处理过程梳理如下:
    • 中断发生,查询中断向量表得到irq
    • 获取对应的irq,进入asm_do_IRQ进行处理
      • 保存现场—将原来的寄存器保存在old_regs中
      • 找到对应的irq_desc,进行相应中断处理
      • 恢复现场—将原有的寄存恢复
  • handle_edge_irq-边沿触发中断
    • 在asm_do_IRQ中可以看到系统在处理中断的一个大致流程,在Linux中,对中断的触发形式,设置了边沿触发和水平触发两种形式。如果中断设置了边沿触发的形式,则在处理过程中会通过handle_edge_irq来调用相应的中断处理函数对中断进行处理。
    • 开始进行中断处理
      • 通知中断控制器开始处理中断,并置位状态
      desc->chip->ack(irq);
      desc->status |= IRQ_INPROGRESS;//处理程序期间
      ```d
      
    • 如果desc的状态不为IRQ_PENDING,则会一直迭代执行
      do {
          struct irqaction *action = desc->action;
          irqreturn_t action_ret;
          ...
          desc->status &= ~IRQ_PENDING;
          spin_unlock(&desc->lock);
          action_ret = handle_IRQ_event(irq, action);//执行action处理函数
          if (!noirqdebug)
          note_interrupt(irq, desc, action_ret);
          spin_lock(&desc->lock);
      } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
      

中断的管理

  • init_IRQ—初始化

    开机启动时会进行一些中断注册

    • 初始化中断体系结构
    for (irq = 0; irq < NR_IRQS; irq++)
          irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
    
    • 根据体系结构进一步初始化
    init_arch_irq();
    
  • request_irq—注册IRQ
    int request_irq(unsigned int irq, irq_handler_t handler,
    	unsigned long irqflags, const char *devname, void *dev_id)
    
    • 参数

      • irq:分配的中断标识
      • handler:中断处理函数
      • irqflags:中断类型标识
      • devname:申请设备的ascll名称
      • dev_id:传递给handler的cookie(全局唯一)
    • 合法性检测

      • 如果是共享中断,则dev_id必须是指定的
      if ((irqflags & IRQF_SHARED) && !dev_id)
          return -EINVAL;
      
      • irq不能超过最大数量
      if (irq >= NR_IRQS)
          return -EINVAL;
      
      • irq_desc的状态不能为IRQ_NOREQUEST
      if (irq_desc[irq].status & IRQ_NOREQUEST)
          return -EINVAL;
      
      • 没有处理函数
      if (!handler)
          return -EINVAL;
      
    • 设定irqaction

      action->handler = handler;
      action->flags = irqflags;
      cpus_clear(action->mask);
      action->name = devname;
      action->next = NULL;
      action->dev_id = dev_id;
      
    • 将irq和action进行关联

      retval = setup_irq(irq, action);
      
  • setup_irq

    将新注册的action,挂载到irqaction上

    • 根据irq,获取指定的irq_desc
      通过链表链接的方式,使得中断处理函数的调用顺序和注册顺序保持一致
      struct irqaction *old, **p;
      p = &desc->action;
      old = *p;
      if (old) {
          ...
          do {
              p = &old->next;
              old = *p;
          } while (old);
          shared = 1;
      }
      *p = new;
      
    • 设置触发方式&中断处理时的一些细节信息
      if (!shared) {
            irq_chip_set_defaults(desc->chip);
            //如果新的action下的触发方式是需要修改的
            if (new->flags & IRQF_TRIGGER_MASK) {
                if (desc->chip && desc->chip->set_type)
                    desc->chip->set_type(irq,new->flags & IRQF_TRIGGER_MASK);
            }
            ...
            ...
      }
      
  • free_irq
    Linux中会首先通过硬件相关函数chip->shutdown通知中断控制器该IRQ已经删除,然后使用free_irq来对相关结构体进行释放
    • 获取相应的irqaction
      desc = irq_desc + irq;
      p = &desc->action;
      
    • 遍历链表逐一的进行释放
      for (;;) {
          struct irqaction *action = *p;
          if (action) {
              struct irqaction **pp = p;
              p = &action->next;
              if (action->dev_id != dev_id)
                  continue;
              *pp = action->next;
              ...
              if (!desc->action) {
                  desc->status |= IRQ_DISABLED;
                  if (desc->chip->shutdown)  
                      desc->chip->shutdown(irq);
                  else
                      desc->chip->disable(irq);
                  }
              unregister_handler_proc(irq, action);
              ...
              kfree(action);
              return;
          }
      
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值