Linux 中断架构
OOP ~
Preface
这里准备介绍一下 Linux 中断的设计思想,当然,也会涉及到具体的中断过程,但是前人已经总结的非常好了,我等就不必重复劳动了。
内核设计结构的时候,我认为是区分哪些是改变的,哪些是不变的,然后把变化的抽象为一个不变的结果,把它们抽象为结构,和不变的固化在内核中,然后不断的修正,优化。
这也说明了一点,没有一开始的设计就是完美的,我们只有一个大概的理念,然后不断地完善它,所以能一开始就设计好的,必然是对整体都了解非常透彻的,这类人我们也称为 SE,当然他们的代码实力不一定强到哪里去就是了。
一个比较典型的就是 VFS 的设计,它将所有的文件系统的操作全都抽象到 file_operations, 并且抽象出 inode, dentry, superblock, 有些文件系统本身设计上并没有这些结构,但是这些就要求驱动必须实现转换,这也是为什么 Linux 原生的文件系统会更有优势就在于此,当然,对于文件操作函数那些有些都是必须实现的,这样设计的好处就在于内核可以不做修改,专注于逻辑上的设计,而不是专注于不同的文件系统的操作上的不同,这就是框架的作用,先将不同给屏蔽,然后专注于整体的逻辑上的设计。
文件系统设备节点,以及自己的块设备。
再一个是驱动的架构,内核专注的一点,应该是在驱动的匹配,管理上,而不应该是由于不同的驱动而来的特殊操作,所以一个优秀的内核就应该先将不同的部分给抽象,然后专注于一些更加重要的部分。这部分可能是出错的恢复,日志,使用优化,以及管理上的需求。
OOP 的思想正是如此,抽象出不同的部分,内核正是无处不体现的着 OOP 的思想。
中断的大致设计
首先区分有哪些是变化的,
中断控制器如何应答中断,如何获取中断号
无数使用中断的设备,以及它们配置中断的方式
中断向量表的配置(架构相关)
但这些变化的实现的目标都是一样的,得到中断号,开关中断,特权级设置等等,但是它们的具体实现都是不一样的,这时候内核就会把目标抽象为一个函数指针,让不同的中断控制器来注册,内核本质就是一个 Abstraction Core
所以内核设计一系列的接口,request_irq enable_irq disable_irq ... 这些都是抽象出来的接口,让需要使用中断的模块注册回调函数,实现起来就类似这样
对于调用函数的模块来说,irq_chip 是透明的,可能是 GIC v3 v2... 内核关键需要抽象出一个接口,上面有需要 irq_chip 注册的函数指针~ ,借此内核就屏蔽了具体的中断控制器,然后对于 irq -> callback 这一层映射,对于 irq_chip 也是透明的,来看看一些具体的代码吧。
这个结构就是内核抽象出来的,而实际我们如何设计回调函数有一万种方法,我们关键需要理解的是这种透明化的思想,对于变化的,我们抽象出它们的目标,让变化的来注册,内核专注于统筹,专注于流程,而不是专注于变化。
内核只需要提供一个注册函数,让控制器模块把这个结构体的地址传给内核,之后内核只需要建立某一个区域的 irq => 这个地址的映射,即可完成操作
比如在注册的时候加上一个范围即可,实际实现是通过注册 irq_domain 因为内核还实现了 irq 的虚拟化,比如每个控制器都支持 0-255 中断号,实际上会冲突,但是我们再加一层 hw_irq 到 virt_irq 的映射即可实现屏蔽,来看一个实际的例子
中断控制器注册函数,当内核分配中断号(也就是其它模块调用 request_irq 的时候),内核就找到了这个 domain,然后调用 alloc 函数,内核给它的只是一个虚拟中断号,内核只需要知道虚拟中断号对应哪个 domain,即可,然后 virt_irq 对应的 hw_irq 只需要中断控制器来维护。
你只要明白了内核设计的思想,上面的代码实现有非常多方式,映射的话数组都可以实现。
总结一下
中断控制器完成
virt_irq -> hw_irq,向内核注册函数指针内核封装接口,提供给其它所有的模块(包括中断控制器)
还有提一点,不同的架构中断向量表初始化方式不一样,所以初始化在不同的目录下
arch/arm/kernel/irq.c 比如这个目录,忘记是不是这个了,但是我阐述的只是分开实现的这一个事实,然后其中个比如实现了一个 early_irq_init
arch_handler_irq 只是一个函数指针,这时候我们只需要中断控制器来注册,那么中断到来它就可以获取中断号了,它还需要转换为 virt_irq 然后内核就可以根据它来查找自己维护的表,调用其他模块注册的回调函数
所以,中断控制器对于向量表的设置仍然是透明的,对于其它模块注册的回调函数仍然是透明的,它只需要告诉内核,只跟内核互动,这种解耦非常适合学习。
内核需要这种 decoupling 来维持稳定,模块一旦耦合严重,就非常不好维护,那你就不能专心专注逻辑了, 别如多核访问的并发处理,首先我们把耦合带来的复杂给解决,那么其它一些问题才能集中解决,问题只在它的模块解决,也是一种抽象。
不管你看驱动架构,看文件系统架构(VFS)还是到现在服务器后端的一些框架,都有着非常好的设计思想,都会把变化的模块抽象,然后设计一个 Core,专门负责它们的交互,负责它们的解耦,然后自己在专注于逻辑,这里的专注,指所有的参与模块,比如中断控制器可以集中处理我们自己硬件的初始化,中断应答,而其他模块怎么使用,我不需要在乎,因为这不我们应该关心的,其他模块以此类推。
中断虚拟化( Pending )
刚刚的 domain,其实就是一个虚拟化的一个苗头,这是服务器的需求,大型机的中断成千上万,必须得有办法支持中断的虚拟化,其实最初的8259-A 只支持15个中断口,后面公用中断线不也是虚拟的一种手段?只是如今可能在接线上更加灵活配置,比如支持写某一个寄存器来触发,即采用 Message 的中断方式。
... on going
ARM 32位 中断过程
References 里面作者介绍的非常清楚,我这里做一些补充吧,在 x86 架构下,中断到来的时候是非常的简单的,首先如果判断 cs 目前不在特权级,那么就根据 tss 中提供的 sp0 提供的栈基址,push 进 ss sp eflags cs ip(否则根据只入栈 eflags cs ip),这样的做法非常符合常理,也就是说, x86 不会提供多套寄存器( ARM 我们也叫 banked ),而是想办法利用栈来备份,ARM 则是有点不同,它会提供多套备份,中断来临的时候利用的是 banked 寄存器备份
你会发现,当我们进入了向量表指定的函数之后,当前的 lr 和 cpsr 都不是我们自己的,并且 PC 指针也被修改了,现在思考一下我们的目标是什么,备份!现在改变了什么?
pc寄存器cpsr寄存器
这俩个寄存器的值跑哪儿了? 答案是 lr_irq spsr_irq,所以我们跟在 x86 的操作是一样的,只是现在需要入栈在 pc 和 cpsr 对应的位置的,是这俩个寄存器。 所以,在中断处理函数一系列复杂的操作,都是为了备份在 中断之前的所有的寄存器。
push { r0 - r15 } + cpsr,只是其中俩个需要做些处理~ 现在看代码就会有种豁然开朗的感觉,现在我们来关注 SVC 状态下触发的中断,那些基址什么的就不细说了,看主线吧。
注意上面使用的 sp 是 sp_irq,每次中断触发都是一样的,这个值在 cpu_init() 那里初始化,这里我们在 SMP 那一章节来讲讲吧,总之sp指向一个 12 字节的临时栈,上面备份了r0,lr, cpsr 寄存器,r0 备份是因为我们需要使用它,而 lr, spsr_irq 不正是我们需要备份的 pc, cpsr 吗?读者好好理解这句话,那么对这段代码就会有很清晰的理解。
上面的代码首先备份了 r1 - r12,r0 目前指向的是 IRQ_MODE的临时栈,而它的备份也在栈里面,备份了这些寄存器之后,我们就可以肆无忌惮的使用它们了,关键就是 r7 指向了寄存器备份区域的最顶端,它目前的任务是备份,r13 - r15, cpsr, old_r0,读者可以看看 struct pt_regs 结构,其实 72 个字节区域,正好可以保存 18 个寄存器,所以上面的代码就在获取剩下的 5 个寄存器,仔细看就可以知道,lr 是没有被使用的,所以可以直接备份,这里必须得明白一点,我们在中断模式操作的是另外一个 lr_irq,然后 pc 也就是在中断模式备份的那个 lr_irq,sp 指向没有开辟这段区域的位置,cpsr 类似,不解释了,r6 直接赋值为 -1
只要理解了这段代码的目的就是为了保存中断之前的所有寄存器,那么其实非常好阅读的,不然你不明白为什么会这么的绕,下面我们来看看恢复的操作,其实不就是直接弹出嘛。
对于 Thumb 2 的宏还有另外一个版本,那个版本应该是不支持,直接加载内存到 pc,所以使用了 rfe 指令,实现原理是一样的,我也没有遇到过那个情况。这个代码是非常的简单的,之前的备份可能还复杂,但这里却是一目了然,中间那个特殊情况可以忽略,应该是处理器的 BUG
为什么要在 SVC 模式
之所以备份的时候有点复杂,都是因为我们需要切换到 svc 模式,以及 ARM 在中断到来的时候自动帮我们修改了寄存器,而不是帮我们备份到栈中,切换到 svc 模式的时候又带来了寄存器的变换,所以又增加了点难度,那么究竟为何要切换呢?
原因是 irq 模式并没有什么特别,我们没有一定要为它准备一个 4KB 的栈空间,x86 模式也是这样,都是利用当前进程的内核栈,既然都有现成的,为何不用?所以我认为总共的原因有俩点
统一,简化操作
节省中断栈的管理
ARM 汇编的一些知识
那些 IA FA,就不说了,英文简写很容易记住,然后说说 cxfs
References
Last updated