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或处理程序的。 action action链表,用于中断处理函数 status IRQ状态 - 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_desc、irq_chip和irqaction之间的关系
中断处理流程
- 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); } ... ... }
- 根据irq,获取指定的irq_desc
- 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; }
- 获取相应的irqaction