Perface
Hi ALL!
最近在定位一个关于中断的问题,以前看待这些问题是割裂的,要不是软件怎么设计中断,要不是硬件怎么设计中断。
正好乘着这次机会,站在软硬件交互的角度将中断的知识来梳理一下。
对了,今天天气不错,周末快乐!
中断是什么?
中断是指计算机在执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得CPU暂时中断当前正在执行的程序而转去执行相应的中断服务程序(Interrupt Service Routine, ISR)。这个事件可以是由硬件设备触发(如硬盘读写错误、打印机没有纸等),也可以是由软件触发(如用户按下Ctrl+C)。
当发生中断时,CPU会暂停当前正在执行的程序,并跳转到相应的中断处理程序(Interrupt Service Routine, ISR)去处理这个事件。中断处理程序通常会保存当前任务的上下文信息(如CPU寄存器的状态、内存中的数据等),然后执行相应的中断处理操作(如读取数据、发送响应等)。在中断处理完毕后,CPU会恢复之前保存的上下文信息,并回到被中断的任务继续执行。
中断是一种异步的事件处理机制,可以提高系统的并发处理能力。在现代计算机系统中,中断被广泛应用于处理各种硬件设备事件和软件异常情况。
我们知道中断的发生与处理是一个需要软硬件交互的操作。
- 关中断:进入不可响应中断请求的中断,由硬件自动完成
- 保存断点:把当前的程序计数器PC中的内容保存起来,用于中断处理结束后能继续执行主程序
- 识别中断源:有多个中断源同时请求时,只能响应最高优先级的,因此需进一步判断中断源
- 保存现场和屏蔽字:进入中断服务程序后,要先保存现场
- 设置新的屏蔽字:用于改变中断优先级和控制中断的产生
- 开中断:执行中断程序时,打开中断可实现更高优先级的中断响应,实现中断嵌套
- 执行中断服务程序:执行中断服务程序中的内容
- 再次关中断:使得恢复现场和屏蔽字时不会被中断打断
- 恢复现场和屏蔽字:使现场和屏蔽字恢复到中断之前的状态
- 再次开中断:中断执行完,现场恢复完后,可以打开中断
- 中断返回:返回原程序继续执行
处理过程,会经历两次的开/关中断,也可用下图描述
在之前我们在做嵌入式的时候,大多是关注了软件层面,如何注册中断,如何设计中断的参数,如何编写中断服务函数,如何将中断服务绑定到中断号。做linux中断的话,这些都是定义好的,你可以去调用接口,当然也支持自定义添加新的中断。
后面这部分可以好好展开学习一下,当然是在我的内存系列结束后。
但是既然是一个软硬件交互的过程,那么这里就拉着硬件和软件两位伙伴来看看一个完整的中断过程。
前置知识
在开始描述一个中断的软硬件流程之前,我们需要提前预备几个前置知识点。
单重中断与多重中断
- 单重中断在CPU执行中断服务程序的过程中不能被打断。当有新的更高优先级的中断发生时,正在执行的中断服务程序不会被暂停,转而执行新的中断服务程序。
- 多重中断在执行某个中断服务程序的过程中,CPU可以去响应更高的中断请求。这被称为中断嵌套。比如,当一个中断服务程序正在执行时,发生了另一个更高优先级的中断,CPU会暂停当前的中断服务程序,转而执行新的中断服务程序。一旦新的中断服务程序执行完毕,CPU会返回到原来被暂停的中断服务程序,继续执行。
注意:单重中断和多重中断的区别在于“开中断”的设置时间不同。对于单重中断,开中断指令设置在最后“中断返回”之前,意味着在整个中断服务处理过程中,不能再响应其他中断源的请求。
而对于多重中断,开中断指令提前至“保护现场”之后,意味着在保护现场之后,若有更高级别的中断源提出请求,CPU也可以响应,从而实现中断嵌套,这是二者的主要区别。
多重中断与中断屏蔽技术
上面我们知道了单重中断和多重中断的区别:
中断的嵌套不能一直嵌套下去,为了保证正在执行的中断服务程序的完整性和提高系统的效率和响应速度,有时候我们在执行种的时候不想新的中断请求干扰正在执行的中断服务程序来提高系统的效率和响应速度和保证正在执行的中断服务程序的完整性。
那么这个时候就出现了中断屏蔽技术。
1、中断屏蔽技术:主要用于多重中断
多重中断:(中断嵌套)当CPU正在执行某个中断服务程序时,另一个中断源又提出了新的中断请求,而CPU又响应了这个新的请求,暂时停止正在运行的服务程序,转去执行新的中断服务程序,这称为多重中断,又称中断嵌套。
如果CPU对新的请求不予响应,待执行完当前的服务程序后再响应,即为单重中断。
中断系统若要具有处理多重中断的功能,必须具备各项条件。
- 1)提前设置“开中断”指令:一般情况下,CPU进入中断周期后,由中断隐指令自动将EINT置“0”,即关中断。
中断隐指令指CPU响应中断之后,经过某些操作,转去执行中断服务程序的一种操作。
这就意味着CPU在执行中断服务程序中禁止响应新的中断请求。CPU若想再次响应中断请求,必须开中断,
这一任务通常由中断服务程序中的开中断指令实现。
多重中断示意图如下:
- 2)、优先级别高的中断源有权中断优先级别低的中断源。
在满足1的前提下,只有优先级别更高的中断源请求才可以中断比其级别低的中断服务程序,反之则不然。
为了保证级别低的中断源不干扰比其级别高的中断源的中断处理过程,可采用屏蔽技术。
例如,有A、B、C、D 4个中断源,其优先级按A->B->C->D由高向低次序排列。在CPU执行主程序期间,同时出现了B和C的中断请求,由于B级别高于C,故首先执行B的服务程序。
当B的服务程序执行完返回主程序后,由于C请求未撤销,故CPU又再去执行C的中断服务程序。若此时又出现了D的请求,因为D级别低于C,故CPU不响应。当C的服务程序执行完返回主程序后再去执行D的服务程序。
若此时又出现了A请求,因A级别高于D,故CPU暂停对D级中断服务程序的执行,转去执行A级中断服务程序,等A级服务程序执行完后,再去执行D级中断服务程序。上述中断处理示意图如下:
2、屏蔽技术
- 1)屏蔽触发器和屏蔽字:程序中断接口电路中,完成触发器D,中断请求触发器INTR和屏蔽触发器MASK。
在程序中断接口电路中,有三个重要的组成部分:完成触发器D,中断请求触发器INTR和屏蔽触发器MASK。这些组件都扮演着各自的角色,以实现程序中断处理的功能。
完成触发器D:当设备工作完成时,会将D置为1,这表示中断源已经准备好向CPU发送中断请求。
中断请求触发器INTR:当设备发出中断请求时,INTR会被置为1。这个触发器是用来向CPU发送中断请求的。
屏蔽触发器MASK:每个中断源都有一个与之对应的屏蔽触发器,如果该中断源被屏蔽(即MASK=1),那么即使INTR被置为1,CPU也不会响应这个中断请求。换句话说,屏蔽触发器可以阻止特定的中断源向CPU发送中断请求。
所有的屏蔽触发器组合在一起,构成了屏蔽寄存器。
- 当中断源被屏蔽时(MASK=1),此时即使D=1,中断查询信号到来时刻只能将INTR置“0”,CPU接收不到该中断源的中断请求,即它被屏蔽。
- 若该中断源未被屏蔽(MASK=0),当设备工作已完成时(D=1),中断查询信号则将INTR置“1”,表示该中断源向CPU发出中断请求,
- 该信号送至排队器进行优先级判断。显然,对于每个中断请求触发器就有一个屏蔽触发器,将所有屏蔽触发器组合在一起,便构成了一个屏蔽寄存器。屏蔽寄存器的内容称为屏蔽字。屏蔽字与中断源的优先级是一一对应的。
- 2)屏蔽技术可改变优先等级:严格地说,优先级包含响应优先级和处理优先级。响应优先级是指CPU响应各中断源请求的优先次序,这种次序往往是硬件线路已设置好的,不便于改动。
处理优先级是指CPU实际对各中断源请求的处理优先次序。如果不采用屏蔽技术,响应的优先次序就是处理的优先次序。采用了屏蔽技术后,可以改变CPU处理各中断源的优先等级,从而改变CPU执行程序的轨迹。
例如,A、B、C、D 这4个中断源的优先级别按A->B->C->D降序排列,根据这一次序,CPU执行程序的轨迹如下图所示。当4个中断源同时提出
在不改变CPU响应中断的次序下,通过改变屏蔽字可以改变CPU处理中断的次序。例如,更改上述4个中断源的屏蔽字将其处理次序更改为:
CPU在运行程序的过程中,若A、B、C、D 4个中断源同时提出请求,按照中断级别的高低,CPU首先响应并处理A中断源的请求,由于A的屏蔽字是1111,屏蔽了所有的中断源,故A程序可以全部执行完,然后回到主程序。
由于B、C、D的中断请求还未响应,而B的响应优先级高于其他,所以CPU响应B的请求,进入B的中断服务程序。
在B的服务程序中,由于设置了新的屏蔽字0100,即A、C、D可打断B,而A程序已执行完,C的响应优先级高于D,于是CPU响应C,进入C的服务程序。
在C的服务程序中,由于设置了的屏蔽字0110,即A、D可打断C,A已执行于是CPU响应D,执行D的中断服务程序。
D一直做完后回到C程序,C程序执行完后,回到B程序。
B程序做完后,回到主程序。
在中断处理过程中,CPU首先会检查INTR,如果INTR为1,那么CPU会进一步检查对应的屏蔽触发器(MASK),如果MASK也为1,那么该中断源的中断请求会被屏蔽,CPU不会响应;如果MASK为0,那么CPU会响应这个中断请求,转去执行对应的中断服务程序。
这个过程是自动进行的,不需要程序员明确地编写相关的指令。在硬件设计中,程序员需要根据实际需求来设置INTR、D和MASK的值,以实现正确的中断处理。
你是软件程序员还是硬件程序员呢?
3、屏蔽技术的意义
屏蔽技术还能给程序控制带来更大的灵活性。例如,在浮点运算中,当程序员估计到执行某段程序时可能出现“阶上溢”,但又不希望因“阶上溢”而使机器停机,为此可设一屏蔽字,使对应“阶上溢”的屏蔽位为“1”,这样,即使出现“阶上溢”,机器也不停机。
在某些情况下,程序员可能希望在执行某些操作之前禁止其他中断,以防止这些中断干扰正在进行的操作。通过将对应的中断屏蔽触发器设置为1(即屏蔽该中断),程序员可以确保在执行这些操作期间,不会受到来自该中断源的中断请求干扰。
此外,屏蔽技术还可以用于实现优先级控制。例如,如果系统中有多个中断源,并且程序员希望在某些操作完成之前不响应其他低优先级的中断,那么可以通过调整屏蔽字的设置**,改变CPU对不同中断源的处理优先级**。这样,程序员可以更好地控制程序的执行流程和中断处理顺序,从而优化系统的性能和响应速度。
另外,屏蔽技术还可以用于调试程序。当程序中存在某些不可预见的错误时,程序员可以通过设置相应的屏蔽字,禁止某些中断请求,以避免这些错误对程序执行造成干扰。同时,通过查看被屏蔽的中断服务程序表,程序员可以及时发现错误并采取相应的措施进行修复。
屏蔽技术为程序员提供了一种强大的工具,可以更加灵活地控制程序的执行和中断处理过程。通过合理使用屏蔽技术,可以提高系统的稳定性和性能,满足各种复杂的应用需求。
一个中断的一生
如何从软件与硬件的角度去看一个中断,一个中断的完整流程应该是什么样子?
- 创建对应的中断服务函数**(软件):
在编写操作系统或应用程序时,需要为每个中断源创建一个对应的中断服务函数(Interrupt Service Routine, ISR)。这个函数是用于处理特定中断的程序**,当发生中断时,处理器会跳转到这个函数执行相应的操作。 - 将中断服务函数绑定到特定的中断号,也叫中断注册**(软件)**:
在操作系统或硬件系统中,需要将每个中断源与相应的中断号进行绑定。这个过程通常在初始化时完成,以确保当发生中断时能够正确地调用相应的中断服务函数。这个绑定可以通过编程实现,例如在Linux系统中可以使用register_interrupt函数将中断服务函数注册到特定的中断号。 - 模块产生原始中断(逻辑):
当某个硬件设备需要中断处理器服务时,它会向中断控制器发送一个中断请求。这个请求可能由硬件信号线或特定协议(如PCI Express)发送。例如,当硬盘读写错误时,硬盘控制器会向中断控制器发送一个中断请求。
原始中断是指当某个设备或部件需要CPU的注意时,它会通过向CPU发送一个中断信号来请求CPU的注意。这个中断信号是由硬件电路产生的,是电信号,可以被CPU感知到。原始中断通常是由硬件设备或系统自发产生的,例如键盘按键、定时器、打印机等。
- 经过int_mask判断后未被屏蔽从而中断状态拉高 (逻辑):
中断控制器接收到中断请求后,会根据int_mask判断该中断是否被屏蔽。如果未被屏蔽,则将中断状态拉高,以便处理器能够感知到这个中断请求。这个过程中断控制器会根据int_mask中的位图判断当前的中断是否被屏蔽,如果未被屏蔽则将对应的中断状态位拉高。
中断的int_mask是中断掩码。它是一个只读寄存器,用于显示哪些位当前被屏蔽,哪些位未被屏蔽/启用。通过设置int_mask,可以屏蔽或开启某些中断。
- 模块顶层信号 xxx_int拉高后,送给特定中断号对应处理器(CPU&MCU等)的对应bit (逻辑):
当中断状态被拉高后,模块顶层信号 xxx_int 会被拉高,并将这个信号送给特定中断号对应处理器的对应bit。这个过程是由硬件逻辑实现的,通常与处理器的架构和中断控制器的设计有关。例如,在x86架构的计算机中,当某个中断状态被拉高后,对应的处理器会通过APIC总线将对应的中断信号发送给处理器。 - 中断信号拉高后,进入对应bit的中断服务函数**(逻辑)**:
当处理器的对应bit接收到中断信号后,处理器会跳转到相应的中断服务函数去处理这个中断。这个过程是由硬件自动完成的,通常与处理器的架构和操作系统的设计有关。例如,在x86架构的计算机中,当处理器接收到一个中断信号后,它会通过CSIP和IVT寄存器跳转到对应的中断服务函数执行相应的操作。
硬件层面,中断系统需要硬件设备(如中断控制器)来捕捉中断信号,并将信号传递给CPU。在接收到中断信号后,CPU会根据中断号在中断向量表中查找对应的入口地址,然后跳转到这个地址执行对应的中断服务程序。这个过程是由硬件自动执行的,不需要软件的参与。
- 执行中断服务函数的内容 (软件):
在中断服务函数中,会根据设备的需求进行相应的操作,例如读取数据、发送响应等。这个过程是由软件实现的,通常与设备的驱动程序和操作系统的设计有关。例如,在Linux系统中,当执行完一个中断服务函数后,它通常会通过调用handle_irq_event函数来处理设备的事件。
MORE
中断向量表
在ARM处理器中,当一个中断信号被触发时,中断控制器会发出一个中断请求,并将该请求传递给CPU。 CPU接收到中断请求后,会根据中断号在中断向量表中查找对应的中断服务程序入口地址。 中断向量表是一个预定义的表,其中每个条目都包含一个指向特定中断服务程序的入口地址。 当CPU接收到中断请求时,它会根据中断号在表中查找对应的条目,获取对应的入口地址,并跳转到该地址执行相应的中断服务程序。 在这个过程中,程序员可以通过设置中断向量表中的条目来定义不同中断源的中断服务程序入口地址。 这样可以实现自定义的中断处理逻辑,根据不同的中断事件执行相应的操作。 需要注意的是,在ARM处理器中,不同的工作模式(例如User、FIQ、IRQ、SVC等)使用不同的寄存器集和特权级别。 当中断发生时,CPU会根据当前的工作模式和中断向量表中的条目,确定要跳转到的中断服务程序的入口地址。 然后它会将CPU的状态保存到相应的栈中,并跳转到该地址执行中断服务程序。 在ARM处理器中,中断向量表是一个预定义的表,它通常在系统启动时由引导加载器(Bootloader)或操作系统进行定义和初始化。 引导加载器在系统启动时负责加载和启动操作系统的内核。在这个过程中,它会读取存储器中的中断向量表数据,并将其复制到指定的内存地址。 这个地址通常是在系统配置时确定的,以确保中断向量表可以在正确的位置被CPU访问。 操作系统在启动后也会接管中断向量表的管理和配置。它会根据系统中断控制器和其他硬件设备的配置,将中断向量表中的条目映射到相应的中断服务程序入口地址。 这样,当中断发生时,CPU可以根据中断号在中断向量表中查找对应的条目,并跳转到对应的地址执行相应的中断服务程序。 需要注意的是,中断向量表的定义方式和具体实现可能会因不同的ARM处理器架构和系统设计而有所不同。 因此,具体的定义位置和方式可能会因硬件平台和操作系统而有所差异。 这部分又涉及到编译与内存分布了,这里就不展开讲了,有机会的一起看看。
Raw Interrupt和Masked Interrupt
Raw Interrupt和Masked Interrupt是两种中断类型,它们在ARM处理器中被用来处理中断。
Raw Interrupt(原始中断)是指外部中断源的状态,无论ARM芯片是否屏蔽该中断源,这个中断源的中断状态都会被寄存器存储,从而可以通过相应的函数读取。
Masked Interrupt(屏蔽中断)是指是否屏蔽的状态。在ARM处理器中,每个中断源都有一个与之对应的屏蔽触发器,如果该中断源被屏蔽(即MASK=1),那么即使INTR被置为1,CPU也不会响应这个中断请求。换句话说,屏蔽触发器可以阻止特定的中断源向CPU发送中断请求。所有的屏蔽触发器组合在一起,构成了屏蔽寄存器。
参考资料
希望看完有所收获,如果内容有BUG,敬请指正。
感谢前辈师兄们优秀书籍和博客分享。
- 嵌入式基础知识-中断处理过程
- 中断屏蔽技术
- 《Linux手册》
- 《深入Linux设备驱动程序内核机制》
- 《奔跑吧Linux内核》
- 中断屏蔽技术