目录
一、中断信号的起点、中断控制器的产生
(1)中断信号的起点
(2)中断控制器的诞生
(3)中断控制器中的两个关键寄存器:IRR、IMR
二、中断响应周期:CPU与中断控制器的交互
(1)CPU初步检查
(2)CPU向中断控制器索要中断向量编号
三、中断周期:中断服务函数的处理过程
(1)关闭CPU中断,保护栈指针的唯一准确
(2)保存重要信息
(3)查找中断向量表:更新 PC 为服务程序入口地址
(4)开启CPU中断,支持中断嵌套
(5)根据中断服务函数的地址,压入新函数栈帧
四、两层中断屏蔽的意义
(1)中断控制器端的屏蔽(IMR,中断屏蔽寄存器)
(2)CPU 端的屏蔽(IF 标志位,EFLAGS 寄存器中的中断允许位)
在计算机中,中断是软件、硬件协同实现的机制。它的出现让CPU能在执行主程序的同时,实时响应外设的紧急请求,也是操作系统处理IO事件、硬件异常、信号等的基础。
我们往往只知道有中断这个机制的作用,但却忽略了它实现的底层原理:如何保存上下文?保存到哪里?中断服务函数又是个什么东西?本篇文章将一步步带你揭开中断的神秘面纱。
一、中断信号的起点、中断控制器的产生
(1)中断信号的起点
中断的起点是外设主动发送的一个电信号请求,当一个外设(比如磁盘、网卡、键盘等)完成任务或遇到异常的时候,该硬件系统被设计成会在自己的中断线上发送一个中断请求信号。
而前面我们说CPU会在每一个执行周期结束后,监测中断线上是否有中断请求,如果有则立马针对该请求进行处理。但你有没有发现一个问题:随着外设的逐渐增多,CPU用于检测中断线的时长也越长(因为需要遍历每一根中断线),而且还不知道怎么判断各种外设的中断优先级,这样会极大的拖慢CPU的性能。
(2)中断控制器的诞生
于是人们想,能不能有一个人帮CPU做完这些事情呢?于是中断控制器应运而生,他把CPU从原来的繁琐遍历检测过程合并成一个,中断控制器首先对接各种外设,只输出一根线给CPU,那么CPU每次只用检测一根线,运行效率大大提升。
现代的中断控制器通常可以分为APIC、NVIC两种。其中APIC主要用在x86架构下的桌面电脑上,重点解决多核心之间的中断调度问题(不同的调度带来的速度差异是大大不同的);而NVIC则多用于ARM架构的单核CPU,主打低功耗、高实时性。
(3)中断控制器中的两个关键寄存器:IRR、IMR
当硬件产生的中断信号倍中断管理器接受后,并且没有被屏蔽,则进入优先级仲裁,中断管理器会筛选出此时优先级最高的中断,并向CPU发送中断请求信号INTR。
二、中断响应周期:CPU与中断控制器的交互
这是 CPU 与中断控制器之间的 “握手” 阶段,核心目的是获取中断的具体类型。
(1)CPU初步检查
CPU在每一个执行周期后都会进入中断周期的检测,即采样INTR信号。并对该信号的屏蔽、优先级做出判断:
- 若 PSW 中的中断屏蔽位 IF=0(关中断),则忽略该请求;
- 若 IF=1(开中断),且当前 CPU 优先级低于中断请求优先级,则进入中断响应总线周期。
(2)CPU向中断控制器索要中断向量编号
这一步是 CPU 与中断控制器的总线交互,不同架构略有差异:
x86 架构(APIC):CPU 发送 2 个连续的
INTA#(中断响应)信号;中断控制器在第二个INTA#时,将中断类型码(8 位,对应 0~255 级中断)发送到数据总线。ARM 架构(NVIC):CPU 检测到中断请求后,直接向 NVIC 请求向量编号;NVIC 通过内部总线将向量编号传递给 CPU,无需外部 INTA 信号。
核心寄存器动作:CPU 将收到的中断类型码 / 向量编号暂存到内部临时寄存器,用于后续查找中断向量表。
三、中断周期:中断服务函数的处理过程
(1)关闭CPU中断,保护栈指针的唯一准确
虽然我们说中断是可以根据优先级进行嵌套的,但是这里是为了保存之前的运行环境信息,这一步是万万不能被中断的,否则可能上一个函数的栈帧还没有保存好就来了下一个中断,最后的结果就是返回时前面的函数无法继续执行了。
(2)保存重要信息
由于所有的函数都是在栈帧中执行的,所有的数据也都被存放在各自的栈帧中,那么当中断来临时,只需要让栈帧继续往低地址延伸,并保存重要关键的运行数据即可,而不用动之前的函数栈帧,就相当于保存了原本的数据信息。
保存栈顶指针的值,等会它来定位回归到原函数:
- CU 发出微命令
SPout, MARin:将 SP 的值(栈顶地址)送入 MAR;- CU 发出微命令
SP=SP-字长:修改 SP 的值(栈顶指针下移,腾出存储单元,字长取决于 CPU 架构,如 32 位 CPU 字长 4 字节);- CU 发出微命令
PCout, MDRin:将 PC 的值(断点地址,原程序下一条指令地址)送入 MDR;- CU 发出微命令
MemWrite:将 MDR 中的断点地址写入 MAR 指向的堆栈内存单元(完成压栈)。而其他信息的保存与这个过程不能说一模一样,只能说毫无区别。
最为主要的就是PSW寄存器的内容,他里面存放了当前函数的执行到的地方等重要数据。
(3)查找中断向量表:更新 PC 为服务程序入口地址
中断向量表中存放的都是各个中断服务函数的函数指针(也可以叫做函数地址),且中断向量表本质就是一个指针数组,是根据优先级中断号顺序编写的,所以可以通过中断向量的编号直接偏移找到对应的中断服务函数地址。
(4)开启CPU中断,支持中断嵌套
这和最开始关闭中断响应的操作是互补的,他们两步骤形成了一个中断屏障,保护了中断响应过程中的栈顶指针、函数运行状态参数的安全。而开启后就能支持更高优先级的中断继续打断当前中断咯。
(5)根据中断服务函数的地址,压入新函数栈帧
当中断服务函数执行结束后,会把最上层的函数栈帧销毁,根据刚刚保存的数据恢复到上一层函数的具体位置继续执行。
四、两层中断屏蔽的意义
细心的同学可以发现:在上述的过程中,中断控制器和CPU都分别有一些寄存器用于中断屏蔽。但他们二者有什么区别呢?
(1)中断控制器端的屏蔽(IMR,中断屏蔽寄存器)
这是第一层屏蔽,属于外设级别的精细控制。
- IMR 的每一位都对应一个具体的外设中断源,比如磁盘、网卡、键盘等。
- 它的作用是 “拦截” 特定外设的中断请求,让这些请求无法进入中断控制器的优先级仲裁环节,更不会被发送给 CPU。
- 举个例子:如果我们屏蔽了键盘中断,那么即使你按下键盘,这个中断请求也只会被记录在 IRR 里,不会被提交给 CPU,也就不会触发中断处理。
(2)CPU 端的屏蔽(IF 标志位,EFLAGS 寄存器中的中断允许位)
这是第二层屏蔽,属于全局级别的总开关。
- CPU 通过 EFLAGS 寄存器中的 IF(Interrupt Flag)位来控制是否响应外部可屏蔽中断。
- 当 IF=0 时,CPU 会忽略所有通过 INTR 信号线发来的中断请求,相当于给整个系统的中断响应按下了 “暂停键”。
- 举个例子:在执行关键的原子操作或中断服务函数的开头时,操作系统会暂时关闭 IF 位,避免被其他中断打断,保证操作的原子性。这一步主要是用来保护中断响应过程时的各种数据安全性(保护犯罪现场)。