一. Linux中断概述
1.1 中断分类
根据中断的来源,中断可分为内部中断和外部中断,内部中断的中断源来源于CPU内部(软件中断指令,溢出,除法错误,例如,操作系统从用户态切换到内核态需要借助CPU内部的软件中断),外部中断的中断源来源于CPU外部,由外设提出请求。
根据中断是否可以屏蔽,中断可分为可屏蔽中断和不可屏蔽中断(NMI),可屏蔽中断可以通过设置中断控制器寄存器等方法被屏蔽,屏蔽后,该终端不再得到响应,而不可屏蔽中断不能被屏蔽。
根据中断入口跳入方法的不同,中断可分为向量中断和非向量中断。采用向量中断的CPU通常为不同的中断分配不同的中断号,当检测到某个中断号的中断到来后,就自动跳转到与该中断号对应的地址执行,不同中断号的中断有不同的入口地址。非向量中断的多个中断共享一个入口地址,进入到该入口地址后,再通过软件判断中断标志来识别具体是哪个中断。
1.2 Linux中断处理机制
顶半部用于完成尽量少的比较紧急的功能,它往往只是简单地读取寄存器的中断状态,并在清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去,这样顶半部执行的速度就会很快,从而可以服务更多的中断程序
二. Linux中断编程
一般来说,linux设备驱动编程中中断涉及到两部分:
(1)中断向量号的申请(亲和CPU和不亲和两种情况)
(2)中断请求(中断线程化或者中断服务函数注册)
1.1 申请中断向量号(with affinity)
对于需要亲和CPU的中断向量而言,申请中断向量后会出现两个现象:
(1)一个CPU对应一个中断向量,即每个中断向量对应的中断服务函数由专一的CPU来处理
(2)多个CPU对应一个中断向量,即每个中断向量对应的中断服务函数由两个或多个CPU来处理
1.1.1 pci_alloc_irq_vectors_affinity
int
pci_alloc_irq_vectors_affinity
(struct pci_dev *dev, unsigned int min_vecs,unsigned int max_vecs, unsigned int flags, struct irq_affinity *affd)
(1)pci_dev *dev是pci device
(2)int min_vecs为所申请的中断向量的最小数
(3)int max_vecs为所申请的中断向量的最大数
(4)用来指明申请向量的属性,比如PCI_IRQ_AFFINITY,PCI_IRQ_MSIX,PCI_IRQ_MSI或PCI_IRQ_LEGACY
(5)irq_affinity *affd包含前向中断数,后向中断数以及自添加的计算函数
- irq_affinity *affd参数跟中断亲和性相关,可以设置pre_vectors实现前几个中断向量不具备cpu亲和性,post_vectors实现后几个中断向量不具备cpu亲和性,不过不亲和的数量一定要大于int min_vecs参数,不然内核认定不给分配向量,见图2
图 1
图 2 irq_calc_affinity_vectors在一般情况下计算出的中断向量数将环境中cpu的数量考虑进去了
- int flags参数中一定要包含PCI_IRQ_AFFINITY才能开启中断亲和性功能,另外还需要包含PCI_IRQ_MSIX,PCI_IRQ_MSI,PCI_IRQ_LEGACY其中一个中断类型,或者均包含设置为PCI_IRQ_ALL_TYPES
- 图1中nr_sets默认值为1(标志亲和的方法只有一种),set_size数组各个成员表示各亲和方法下需要分配的亲和向量中断(默认只有set_size[0]有值)
- 当申请的亲和向量中断数小于环境中numa node的数量,内核会秉持着物尽其用的原则,将每一个numa节点的cpu都散布出去,这意味着会出现一个中断向量会有多个numa节点上的cpu响应
- node_to_cpumask作为二级指针存储着各个numa ID作为索引值,最终指向各个numa 节点的cpu mask.
- node_vectors根据node_to_cpumask计算出每个numa节点应该分配到的中断向量数
- 需要提一句的是总的亲和性中断向量数numvecs再一次被remain cpu数限制,另外numa是经过上升排序了的,可能会造成中断向量不从numa0开始
- 为每个中断向量分配中断cpu的核心算法,可能会出现一个numa节点中前几个中断向量每个都分配到了2个以上的cpu,后面的每个中断向量只分配到了一个cpu
- pre_vectors和post_vectors对应的irq_affinity_desc也需要初始化cpumask,均被初始化为irq_default_affinity,即表示为不与任何cpu亲和
- 每一个中断向量的准备工作统一由pci_msi_setup_msi_irqs实现,最终调用钩子函数ops->domain_alloc_irqs,具体为msi_domain_ops_default
1.2 中断请求
Linux命令行cat /proc/interrupts可以显示每个中断向量对应亲和的CPU以及CPU执行某一个中断向量的服务函数的计数值以及对应的向量名
1.2.1 pci_request_irq
int
pci_request_irq(struct pci_dev *dev, unsigned int nr, irq_handler_t handler, irq_handler_t thread_fn, void *dev_id, const char *fmt, ...)
(1)pci_dev *dev是当前的pci设备
(2)int nr是pci设备内部的中断向量索引,用来计算实际的中断号:通过pci_irq_vector(dev, nr)
(3)handler是注册的中断服务函数,执行于中断上下文;另外IRQF_SHARED表示可由多个设备共享该中断向量
(4)thread_fn对应的注册函数则执行于内核线程,执行该函数不可避免的需要进程切换开销;另外IRQF_ONESHOT可以保证thread_fn函数执行时由内核自动屏蔽对应的中断,从而保证thread_fn函数执行完整不被打断,执行完成之后再重新使能该中断
(5)dev_id用于传参入handler或thread_fn注册的函数
(6)fmt是中断向量的名字
三. 中断数据处理结构
Linux内核中处理中断主要有三个数据结构,irq_desc,irq_chip和irqaction。
在\include\linux\ irq.h中定义了
2.1 irq_desc
irq_desc用于描述IRQ线的属性与状态,被称为中断向量描述符。需要特别说明的是:irq_desc在申请中断向量的时候就已经分配好了,并悬挂在irq_desc_tree上,可直接由中断向量号直接寻址到对应的irq_desc(由irq_to_desc),具体到msi_domain_ops-> domain_alloc_irqs的回调函数
struct irq_desc *desc:
2.2 irq_data
struct irq_data包含chip中断控制器,中断域irq_domain
2.3 irqaction
在\include\linux\interrupt.h中定义了 irqaction用来描述特定设备所产生的中断描述符,因为IRQF_SHARED带来的中断向量被多个设备共享的问题
struct irqaction *action: