IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 一起分析Linux系统设计思想——05中断框架剖析(二) -> 正文阅读

[数据结构与算法]一起分析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;
	}
	...
}

中断处理流程下回分解。。。


恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章           查看所有文章
加:2021-08-29 09:37:13  更:2021-08-29 09:39:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 23:41:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码