中断是大家用的最多的功能,不管是单片机还是 Linux 系统,都需要用到中断,对它的深入理解是非常必要的。
为什么需要中断?
答案:处理器的速度比外设快很多,内核必须要处理其他任务,只有当外设准备好了,CPU才转过来处理外设的事务。
一般通讯方式为:轮询(polling)、中断(interrupt),除了网络传输适合用轮询外,一般其他情况都是用中断。
中断分类
中断是指 CPU 正常运行期间,由于内外部事件或程序预先安排的事件,引起的 CPU 暂时停止正在运行的程序,转而为该内部或外部预先安排的事件服务的程序中去,服务完毕后再返回去继续执行被暂时中断的程序。
常说的中断其实是第一种,异步中断。
陷阱就是系统调用,从用户态陷入到内核态,比如调用 open、write 等系统调用,也算中断。这两种很正常,所以会返回到下一条指令。
故障就是遇到了内存缺页等情况,会返回到当前指令继续执行,看看内核是否会修复完毕,如果修复不了就挂掉了。终止就是系统直接挂掉了。
中断子系统硬件架构
一个完整的设备中,与中断相关的硬件可以划分为3类,它们分别是:设备、中断控制器和CPU本身。
设备:设备是发起中断的源,当设备需要请求某种服务的时候,它会发起一个硬件中断信号,通常,该信号会连接至中断控制器,由中断控制器做进一步的处理。在现代的移动设备中,发起中断的设备可以位于soc(system-on-chip)芯片的外部,也可以位于芯片的内部,因为目前大多数 soc 都集成了大量的硬件 IP,例如 I2C、SPI、Display Controller 等等,就是内部中断源。
中断控制器:中断控制器负责收集所有中断源发起的中断,现有的中断控制器几乎都是可编程的,通过对中断控制器的编程,我们可以控制每个中断源的优先级、中断的电气类型,还可以打开和关闭某一个中断源,在smp系统中,甚至可以控制某个中断源发往哪一个 CPU 进行处理。对于 ARM 架构的 soc,使用较多的中断控制器是VIC(Vector Interrupt Controller),进入多核时代以后,GIC(General Interrupt Controller)的应用也开始逐渐变多。STM32 单片机的中断控制器叫 NVIC,ARM架构的中断控制器一般为GIC,不同架构有不同的中断控制器。
CPU:最终响应中断的部件,它通过对可编程中断控制器的编程操作,控制和管理者系统中的每个中断,当中断控制器最终判定一个中断可以被处理时,他会根据事先的设定,通知其中一个或者是某几个 cpu 对该中断进行处理,虽然中断控制器可以同时通知数个 cpu 对某一个中断进行处理,实际上,最后只会有一个 cpu 相应这个中断请求,但具体是哪个 cpu 进行响应是可能是随机的,中断控制器在硬件上对这一特性进行了保证,不过这也依赖于操作系统对中断系统的软件实现。
为什么需要中断控制器?
CPU 要做的事情主要是运算。一个 CPU 有很多个中断可以使用,他们之间也有优先级。由于中断过多,我们需要中断进入 CPU 处理之前,先进入中断控制器,让中断控制器来控制中断的优先级、触发方式、enable 和 disable等,为CPU减轻负担,让CPU专注于运算。
中断控制器的级联
根据中断数量的不同,中断控制器可以级联,以此来满足需求。比如在 GIC 中断控制器之前都会连接 EINT 中断控制器,或者其他中断控制器,对不同的中断分级管控。
中断控制器的级联有两种类型:
机器级别的级联,级联的初始化代码理所当然地位于板子的初始化代码中(arch/xxx/mach-xxx),因为只要是使用这个板子或SOC的设备,必然要使用这个子控制器。
设备级别的级联,因为该设备并不一定是系统的标配设备,所以中断控制器的级联操作应该在该设备的驱动程序中实现。
机器设备的级联,因为得益于事先已经知道子控制器的硬件连接信息,内核可以方便地为子控制器保留相应的 irq_desc 结构和 irq 编号,处理起来相对简单。
设备级别的级联则不一样,驱动程序必须动态地决定组合设备中各个子设备的 irq 编号和 irq_desc 结构。我只讨论机器级别的级联,设备级别的关联可以使用同样的原理。
中断子系统架构
整个中断子系统的架构分为 4 层,最底层(第四层)为硬件,包括 CPU、中断控制器。第三层是 CPU 的驱动和中断控制器的驱动,由芯片原厂负责。第二层是 Linux 内核提供的通用中断处理模块,这一层存在的意义在于,希望用户在第一层写的驱动在移植的时候更方便,保持接口不变,不让用户直接使用芯片原厂的 API,而是 Linux 的 API。第一层就是驱动工程师日常写的驱动啦。