一起分析Linux系统设计思想——05中断框架剖析
Posted 穿越临界点
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起分析Linux系统设计思想——05中断框架剖析相关的知识,希望对你有一定的参考价值。
在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
我们在申请中断时究竟是在做什么?共享中断是如何实现的?在本篇中你都可以找到答案。
3 中断注册和注销
3.1 中断注册
前文中断初始化过程已经给irq_chip和handle_irq赋过值了。本节主要的目的就是给irqaction赋值。
涉及到的最重要的接口就是 request_irq ,该函数主要完成了下述几个重点工作。
- 注册指定中断号对应的后处理函数(基于链表技术实现中断号共享功能)。
- 调用setup_irq实现中断方式设置。
- 调用setup_irq实现中断使能。
/* kernel/irq/manage.c */
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
struct irqaction *action;
int retval;
...
/* 共享中断号时必须使用dev_id进行区分到底是哪个中断,
在共享中断处理和注销过程中我们会更深入地理解dev_id的用途 */
if ((irqflags & IRQF_SHARED) && !dev_id)
return -EINVAL;
if (irq >= NR_IRQS) /*超过最大中断号时返回错误*/
return -EINVAL;
if (irq_desc[irq].status & IRQ_NOREQUEST) /*该中断号不允许申请时返回错误*/
return -EINVAL;
if (!handler) /*空指针返回错误*/
return -EINVAL;
/* 分配链表节点空间 */
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
if (!action)
return -ENOMEM;
/* 依据函数入参填充action节点内容 */
action->handler = handler; /*填充中断后处理函数*/
action->flags = irqflags; /*填充flags,可以理解为该action的属性*/
cpus_clear(action->mask);
action->name = devname;
action->next = NULL; /*标记为链表尾*/
action->dev_id = dev_id; /*共享中断使用dev_id来区分不同的中断(action节点)*/
select_smp_affinity(irq); /*亲和性设置*/
...
/* 调用setup_irq完成剩余的工作 */
retval = setup_irq(irq, action);
if (retval)
kfree(action);
return retval;
request其实并没有做太多实质性的工作,大部分工作都是委托setup_irq来做的。
- 从中我们可以看到内核的封装思想。外部函数尽量设计的简单,暴露尽量少的细节,如果细节过多就再封装一个内部函数来实现。
- 我们还可以学到的一点是,当结构体入参比较复杂时,可以再创建一个入参为结构体成员的外部函数给外部调用者使用。
接下来,我们一起分析下setup_irq函数。
/* kernel/irq/manage.c*/
/*
* Internal function to register an irqaction - typically used to
* allocate special interrupts that are part of the architecture.
*/
int setup_irq(unsigned int irq, struct irqaction *new)
struct irq_desc *desc = irq_desc + irq; /*注意这里是用指针运算来访问结构体数组的技术*/
struct irqaction *old, **p;
const char *old_name = NULL;
unsigned long flags;
int shared = 0;
if (irq >= NR_IRQS)
return -EINVAL;
if (desc->chip == &no_irq_chip)
return -ENOSYS;
...
/*
* The following block of code has to be executed atomically
*/
spin_lock_irqsave(&desc->lock, flags); /*lock自旋锁*/
p = &desc->action;
old = *p;
if (old) /*old不为零说明该中断号注册过后处理函数:共享中断模式*/
/* 下面的注释陈述了共享中断必须遵循的两个规则,在申请中断时注意遵守
* Can't share interrupts unless both agree to and are
* the same type (level, edge, polarity). So both flag
* fields must have IRQF_SHARED set and the bits which
* set the trigger type must match.
*/
if (!((old->flags & new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK))
old_name = old->name;
goto mismatch;
...
/* add new interrupt at end of irq queue */
do /*找到链表尾*/
p = &old->next;
old = *p;
while (old);
shared = 1;
*p = new; /*将新节点插入链表尾(非共享模式下是第一个节点)*/
/* Exclude IRQ from balancing */
if (new->flags & IRQF_NOBALANCING)
desc->status |= IRQ_NO_BALANCING;
if (!shared) /*非共享模式(第一次配置action)*/
irq_chip_set_defaults(desc->chip); /*给空指针挂默认函数*/
...
/* Setup the type (level, edge polarity) if configured: */
if (new->flags & IRQF_TRIGGER_MASK) /*配置中断触发模式*/
if (desc->chip && desc->chip->set_type)
desc->chip->set_type(irq,
new->flags & IRQF_TRIGGER_MASK);
else
/*
* IRQF_TRIGGER_* but the PIC does not support
* multiple flow-types?
*/
printk(KERN_WARNING "No IRQF_TRIGGER set_type "
"function for IRQ %d (%s)\\n", irq,
desc->chip ? desc->chip->name :
"unknown");
else
compat_irq_chip_set_default_handler(desc);
desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
IRQ_INPROGRESS);
/* 使能中断 */
if (!(desc->status & IRQ_NOAUTOEN))
desc->depth = 0;
desc->status &= ~IRQ_DISABLED;
if (desc->chip->startup)
desc->chip->startup(irq);
else
desc->chip->enable(irq);
else
/* Undo nested disables: */
desc->depth = 1;
/* Reset broken irq detection when installing new handler */
desc->irq_count = 0;
desc->irqs_unhandled = 0;
spin_unlock_irqrestore(&desc->lock, flags); /*释放自旋锁*/
new->irq = irq;
register_irq_proc(irq); /*生成proc目录下的相关目录*/
new->dir = NULL;
register_handler_proc(irq, new); /*填充new->dir*/
return 0;
mismatch: /*错误处理*/
...
spin_unlock_irqrestore(&desc->lock, flags);
return -EBUSY;
该函数如果需要你自己来实现,我相信大体的流程都是没问题的,其中有几个技巧和需要注意的问题值得我们学习:
- 操作desc[irq]时一定要加锁,否则可能产生竞态。
- 在操作链表时使用了struct irqaction **p二级指针,具体的好处可以参看《C和指针》中的链表操作相关章节。
- 虽然在C语言中不推荐使用goto语句,但在大量类似的错误处理场景中使用goto语句是最好的选择。
3.2 中断注销
中断注销是中断注册的逆动作。流程比较简单,主要就是将irqaction链表中的指定节点移除,如果是最后一个节点的话会附带着disable掉指定irq。
注销接口函数是free_irq(),该函数通过dev_id来识别到底卸载链表中的哪个节点。
/* kernel/irq/manage.c */
void free_irq(unsigned int irq, void *dev_id)
struct irq_desc *desc;
struct irqaction **p;
unsigned long flags;
irqreturn_t (*handler)(int, void *) = NULL;
WARN_ON(in_interrupt()); /*卸载中断不能在中断上下文中进行*/
if (irq >= NR_IRQS)
return;
desc = irq_desc + irq; /*结构体数组的指针访问形式*/
spin_lock_irqsave(&desc->lock, flags); /*获取自旋锁*/
p = &desc->action;
for (;;) /*根据dev_id进行链表遍历*/
struct irqaction *action = *p;
if (action)
struct irqaction **pp = p;
p = &action->next;
if (action->dev_id != dev_id) /*查找dev_id与入参相同的节点*/
continue; /*使用非逻辑+continue,可以减少圈复杂度*/
/* Found it - now remove it from the list of entries */
*pp = action->next; /*删除当前链表节点*/
...
if (!desc->action) /*链表空之后则disable掉当前中断号*/
desc->status |= IRQ_DISABLED;
if (desc->chip->shutdown)
desc->chip->shutdown(irq);
else
desc->chip->disable(irq);
spin_unlock_irqrestore(&desc->lock, flags); /*释放自旋锁*/
unregister_handler_proc(irq, action); /*删除proc中相关目录*/
/* Make sure it's not being used on another CPU */
/* 这一行代码就比较见功力了,不熟悉多核架构,或者心思不缜密是想不起来加类似的处理的 */
synchronize_irq(irq); /*如果是多核CPU,等待其它核上的中断处理完再继续执行下面的语句*/
...
kfree(action); /*释放内存,做链表删除时不要忘记内存释放*/
return;
printk(KERN_ERR "Trying to free already-free IRQ %d\\n", irq);
spin_unlock_irqrestore(&desc->lock, flags);
return;
...
中断处理流程下回分解。。。
恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~
以上是关于一起分析Linux系统设计思想——05中断框架剖析的主要内容,如果未能解决你的问题,请参考以下文章