linux -- 中断管理 -- 中断管理以及PIC可编程中断控制器

文章详细介绍了计算机系统中的中断和异常机制,包括中断与异常的区别、中断处理流程、中断向量表、中断处理的层次结构、中断注册以及设备驱动如何注册中断处理函数。重点讨论了中断处理的HARDIRQ和SOFTIRQ阶段,以及中断共享和中断号探测机制。

1 中断分为同步中断(中断)和异步中断(异常)

1.1 中断和异常的不同

  • 中断由IO设备和定时器产生,用户的一次按键会引起中断。异步。

  • 异常一般由程序错误产生或者由内核必须处理的异常条件产生。同步。缺页异常,断点int3

异常如果由程序错误产生,内核通过发送一个信号来处理异常

如果由内核必须处理的异常条件诱发,那么内核必须执行所需要的所有步骤。

(思考信号产生的动机)

1.2 中断和进程上下文切换的不同

中断切换的代码不是一个进程,而是一个内核控制路径,代表中断发生时正在执行的进程在内核中执行。

1.3 中断敏感性

  • 当内核正打算做一些事情,中断随时可能会到来,因此中断必须很快完成(以便内核处理它的事情),尽可能把多的事情放到后面执行。

  • 中断随时可能到来,在处理一个中断时必须允许其他中断到来。这样可以允许尽量多的IO设备处于忙碌状态。所以中断应该允许嵌套

  • 临界区中,中断必须被禁止。这个要求对于临界区来说是必要的,但是对于内核规则来说是不应当的。所以应该减少临界区。

1.4 中断向量表

非屏蔽中断和异常的向量是固定的,可屏蔽中断的向量可以通过编程来改变。

1.5 IRQ和中断

IRQ(Interrupt request)是设备用来向可编程中断控制器发送中断请求的,IRQ在硬件上和中断控制器相连接。一个设备可能有多条IRQ,如PCI卡有4条。

当设备产生中断信号时,会发送到IRQ线上。中断信号会被中断控制器一直监视,如果有条IRQ线上同时出现中断信号,那么会选取IRQn值最小的那一个优先处理。

中断控制器会将从IRQn上接受来的中断信号转换为对应的向量,将这个向量放到I/O端口上以便CPU需要的时候通过读取数据总线来读。中断控制器将接受到的中断信号转发到CPU的INTR引脚上产生一个中断

接下来就是等待CPU把这个中断信号写进可编程中断控制器的I/O端口来确认这个向量。如果确实是,则清INTR线。

IRQ编号从0开始,IRQn对应的intel的缺省向量为n+32。IRQ和向量的对应关系可以通过一些指令来重新编程。

选择性的禁止IRQ线相当于"服务台"(中断控制器)让客户的需求(设备中断)排队,当开启IRQ时还会来处理需求

通过cli sti来开启和屏蔽可屏蔽中断相当于服务台暂时不上班,客户的需求会被忽略。

1.6 多APIC系统和中断分发方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FnHwwudK-1678462382637)(null)]

  • 静态分发

  • 动态分发 中断在CPU之间分发

CPU产生处理器之间的中断,这在SMP系统中很有用。

如果是单处理器,那么APIC可以弱化成8259A,LIN0 LIN1分别作为INTR和NMI引脚使用

也可以作为一个外部的I/O APIC使用(虽然它在处理器内部),本地APIC被激活。

1.7 中断框架 注册以及调用

中断框架中对 IRQ Line 和 PIC 分别做了抽象。暂且不讨论SMP情形,将外设-中断控制器-处理器这条通路简化为下图:
(不同的硬件会将中断控制器放在不同的位置:如X86的8259放在CPU外,嵌入式领域的SoC一般在芯片内集成)
在这里插入图片描述
一个外部设备要想将中断发送给CPU ,首先要配置好PIC。系统中断处理框架已将提供了PIC配置的接口供设备驱动程序使用(设备可能不是一开始就连接在PIC某一中断引脚上的,所以需要在设备驱动程序中调用PIC配置相关接口)
PIC的配置包含以下工作:

  1. 设定外设的中断触发类型 水平or边沿
  2. 将外设的中断引脚编号映射到处理器可见的软件中断号irq
  3. 屏蔽掉某些外部设备的中断触发
    处理器提供的PIC配置接口最终也是要落实到配置PIC寄存器上的,比如ARM GIC-400提供了一系列控制寄存器来完成以上所有配置工作。
    软件中断号是处理器可见的irq号,用来标识中断到来时从PIC中读取到的中断号码。现代PIC模块多是级联的,也就是说一个SoC上有多个中断控制器,每个中断控制器上都有外部设备接入IRQ Line,一般情况下每个PIC上的IRQ Line编号是一致的,为了区分不同的设备的IRQ编码就需要带上PIC的编码,也就是IRQ Domin的概念,IRQ Line编号在多中断控制器级联的情况下被称为HW Interrupt ID。也就是说这种情况下HW Interrupt ID + IRQ Domin才能标识出一个外设的中断。不管是单PIC还是多PIC级联,最后到处理器面前的只有软件中断号irq。

Linux初始化阶段会准备好中断向量表,其中一箱就是外部设备的中断向量,这是一个通用的外部中断处理函数的入口地址。从该地址运行通用的中断处理函数。进入通用中断处理函之后系统必须知道正在处理的中断是哪一个设备产生的,而这正是由前面提到的中断号irq决定的。
中断向量表的初始化以及通用外部中断处理函数的编写都由操作系统负责

外部中断发生时预先设计好的处理器硬件逻辑往往会做一些特定的动作,为从软件层面发起中断处理做准备。
不同的处理器行为不同,抽象出一个一般路径:

  1. 保存当前任务的上下文寄存器在中断栈中。
  2. 屏蔽处理器响应外部中断的能力
  3. 根据中断向量表找到通用外部中断处理函数入口地址
  4. 跳转

不同的架构平台上,通用的软件处理函数也有不同,一般过程是:

  1. 设法从PIC中得到导致本次中断的外部设备对应的软件中断号irq(一般用汇编实现)
  2. 调用一个C函数(do_IRQ)来处理中断
  3. C函数返回时完成中断现场恢复工作
  4. 被中断的任务开始继续执行

为什么要关中断执行:因为各个设备的中断处理函数在驱动程序中实现,内核无法保证这些函数的执行时间,如果时间过长会导致系统措施很多中断,丢失数据或者操作系统响应过长时间。
为了解决这个问题,操作系统将中断处理过程分为两个部分:HARDIRQ和SOFTIRQ。
HARDIRQ部分在中断关闭的情况下执行,只做最关键的操作,尽可能短。irq_enter函数。
SOFTIRQ部分在中断打开的情况下执行,这个阶段系统仍可以响应外部设备的中断,耗时操作可以放在这个部分。irq_exit中完成

do_IRQ源码:

asmlinkage void do_IRQ(int irq, struct pt_regs *regs)
{
   
   
	struct pt_regs *oldregs = set_irq_regs(regs);

	irq_enter();	//HARDIRQ部分的开始  更新系统中的一些统计量  标识出HARDIRQ上下文
	generic_handle_irq(irq);	//核心
	irq_exit();		//SOFTIRQ部分
	
static inline void generic_handle_irq(unsigned int irq)
{
   
   
	//irq_desc 沟通从通用的中断处理函数到设备特定的中断处理例程ISR之间的桥梁作用
	struct irq_desc *desc = &irq_desc[irq];
	//取出对应的irq_desc对象 运行handle_irq函数
	desc->handl_irq(irq, desc);
}

下图是对irq_desc结构以及irq_desc数组的描述图
在这里插入图片描述
irq_desc数组定义:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
   
   
	[0 ... NR_IRQS-1] = {
   
   
		.handle_irq	= handle_bad_irq,
		.depth		= 1,
		.lock		= __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
	}
};

irq_desc数组的初始化在early_irq_init函数中进行:

int __init early_irq_init(void)
{
   
   
	int count, i, node = first_online_node;
	struct irq_desc *desc;

	init_irq_default_affinity();

	printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS);

	desc = irq_desc;
	count = ARRAY_SIZE(irq_desc);

	for (i = 0; i < count; i++) {
   
   
		desc[i].kstat_irqs = alloc_percpu(unsigned int);
		alloc_masks(&desc[i], node);
		raw_spin_lock_init(&desc[i].lock);
		lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
		mutex_init(&desc[i].request_mutex);
		init_waitqueue_head(&desc[i].wait_for_threads);
		desc_set_defaults(i, &desc[i], node, NULL, NULL);
	}
	return arch_early_irq_init();
}

struct irq_desc结构包含的成员,这个结构体十分重要
比较关键的成员已经注释

struct irq_desc {
   
   
	struct irq_common_data	irq_common_data;
	struct irq_data		irq_data;		//保存软件中断号irq和chip相关数据 包括irq_domain
	unsigned int __percpu	*kstat_irqs;	//per-cpu型成员  统计系统中中断计数
	irq_flow_handler_t	handle_irq;		//函数指针 指向和当前设备中断触发电信号类型相关的函数(指向通用处理函数)
										//边沿触发就对应一个边沿触发类的处理函数 
										//电平触发类的就对应一个电平触发类的处理函数
										//如果没有区分那就提供一个常规处理函数就行
										//handle_irq指向的函数内部才会调用设备特定的中断服务例程
										//不同平台的handle_irq的具体实现在系统初始化阶段提供,设备驱动程序员不必关心
										
	struct irqaction	*action;	/* IRQ action list */ 
									//针对某一个具体设备的中断处理的抽象,是一个链表
									//通过request_irq向其中挂载设备特定的中断处理函数
									//action中有irq_handler_t类型的handler成员,这个才是对应设备中断服务例程ISR
									//设备驱动中安装或卸载的中断处理服务历程ISR也就是装载这个成员里

	//irq_desc结构内部有一个对IRQ Line的操作成员handle_irq
	//还有一个对这条Line上的所有设备的处理函数的成员action
	//handle_irq和action成员负责中断处理的不同部分,handle_irq与irq中断号一一对应,代表了对IRQ Line上的处理动作
	//action代表具体的设备相关的处理函数
	//action是一个链表,多个设备可以共享一条IRQ Line (共享同一中断号irq)所以接到同一链表上,后面IRQ Line上有中断到来时处理器可以遍历这条链表以执行所有处理函数
	
	unsigned int		status_use_accessors;
	unsigned int		core_internal_state__do_not_mess_with_it;
	unsigned int		depth;		/* nested irq disables */
	unsigned int		wake_depth;	/* nested wake enables */
	unsigned int		tot_count;
	unsigned int		irq_count;	/* For detecting broken IRQs */
	unsigned long		last_unhandled;	/* Aging timer for unhandled count */
	unsigned int		irqs_unhandled;
	atomic_t		threads_handled;
	int			threads_handled_last;
	raw_spinlock_t		lock;
	struct cpumask		*percpu_enabled;
	const struct cpumask	*percpu_affinity;
#ifdef CONFIG_SMP
	const struct cpumask	*affinity_hint;
	struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
	cpumask_var_t		pending_mask;
#endif
#endif
	unsigned long		threads_oneshot;
	atomic_t		threads_active;
	wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PM_SLEEP
	unsigned int		nr_actions;
	unsigned int		no_suspend_depth;
	unsigned int		cond_suspend_depth;
	unsigned int		force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry	*dir;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
	struct dentry		*debugfs_file;
	const char		*dev_name;	
#endif
#ifdef CONFIG_SPARSE_IRQ
	struct rcu_head		rcu;
	struct kobject		kobj;
#endif
	struct mutex		request_mutex;
	int			parent_irq;
	struct module		*owner;
	const char		*name;		//查/proc/interrupts文件显示的对应名称
} ____cacheline_internodealigned_in_smp;

来看看struct irq_data结构的irq_data成员保存了哪些

struct irq_data {
   
   
	u32			mask;
	unsigned int		irq;		//软件中断号
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值