一起分析Linux系统设计思想——05中断框架剖析(二)
在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
文章目录
- 3 中断注册和注销
- 3.1 中断注册
- 3.2 中断注销
我们在申请中断时究竟是在做什么?共享中断是如何实现的?在本篇中你都可以找到答案。
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");} elsecompat_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);elsedesc->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);elsedesc->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;}...
}
中断处理流程下回分解。。。
恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
