一起分析Linux系统设计思想——05中断框架剖析

Posted 穿越临界点

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起分析Linux系统设计思想——05中断框架剖析相关的知识,希望对你有一定的参考价值。

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。


我们在申请中断时究竟是在做什么?共享中断是如何实现的?在本篇中你都可以找到答案。

3 中断注册和注销

3.1 中断注册

前文中断初始化过程已经给irq_chip和handle_irq赋过值了。本节主要的目的就是给irqaction赋值。

涉及到的最重要的接口就是 request_irq ,该函数主要完成了下述几个重点工作。

  1. 注册指定中断号对应的后处理函数(基于链表技术实现中断号共享功能)。
  2. 调用setup_irq实现中断方式设置。
  3. 调用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;

该函数如果需要你自己来实现,我相信大体的流程都是没问题的,其中有几个技巧和需要注意的问题值得我们学习:

  1. 操作desc[irq]时一定要加锁,否则可能产生竞态。
  2. 在操作链表时使用了struct irqaction **p二级指针,具体的好处可以参看《C和指针》中的链表操作相关章节。
  3. 虽然在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中断框架剖析的主要内容,如果未能解决你的问题,请参考以下文章

一起分析Linux系统设计思想——05中断框架剖析

一起分析Linux系统设计思想——05中断框架剖析

一起分析Linux系统设计思想——05中断框架剖析

一起分析Linux系统设计思想——05中断框架剖析

一起分析Linux系统设计思想——05内核定时器的使用

一起分析Linux系统设计思想——05内核定时器的使用