ARMv8 中断管理(3): 中断服务程序

从进入 IRQ/FIQ 中断向量开始,中断处理的完整流程:

  1. 保存上下文
  2. 切换中断栈,为进入“真正的”中断服务程序做准备
  3. 执行真正的中断服务程序
  4. 恢复之前的上下文

“真正的”中断服务程序

“真正的”意为不算那些对于所有异常、中断来说都相同的“套话” ,只讨论对于中断特有的行为。

承认一个中断

真正的中断服务程序从接受 CPU Interface 传来的中断 开始算起,这一步的实现通过读取ICC_IAR1_EL1, 返回当前 中断的 INTID。

拿到 INTID 后,就根据不同的 ID 调用各自对应场景下的服务函数, 比如若 INTID 是对应与时钟中断,那么此步需要清楚状态寄存器、 重新开启时钟定时器。

标记中断处理完毕

做完相干的事情后,需要将该中断标记为已完成,方便后面的中断进来, 也就是上一节说的优先级下降中断失效过程。

GICv3 支持将这两步合为一次操作,实际我们也是这样做的,通过写入 ICC_EOI1_EL1寄存器来完成标记处理完成。此中断的状态也就从 active->inactive.

中断服务程序中,承认中断和标记完成两步操作应该是用 while 循环 包裹起来的。

反复的读取 IAR、标记中断已完成… 如果此时该 CPU 上已经没有 中断待处理了,读取 IAR 会返回特殊 INTID: 1023

中断的上下部机制

中断服务函数的停留时间应该越短越好,否则影响其他任务占用 CPU,这是老生常谈的。

以上观点存在的原因是:中断服务函数中是关闭中断的,CPU 只有串行的处理完当前 中断后, 才能继续做下一件事情,即便是高优先级任务也得等待,因为时钟中断被关闭!

所以 Linux 在 2.6 引入了中断的上下部机制,将整个中断服务函数拆分为上部和下部:

  • 上部:那些不能被打断的步骤,比如保存上下文,承认和标记中断完成等
  • 下部:宽松的管理方式,执行过程就算被打断也没关系,指的就是上面说的对应各自中断 应用场景下的服务函数,比如一个按键触发代表的实际行为

ARMv8 如何支持中断上下部

ARMv8 中,进入异常向量是自动关中断的,可执行msr DAIFClr, #imm来手动开启。

所以说,直到手动开中断之前的所有操作都属于中断的上部。

那么,应该在何时开启中断呢?我认为分割后的正确中断处理流程应该是:

  1. 承认一个中断
  2. 根据 INTID 标记其应该做的行为,注意只是标记
  3. 标记该中断完成
  4. 待该 CPU 上的所有中断都完成后,开中断
  5. 遍历检查所有标记,如果有待完成的任务在此时执行

上面说的标记和执行过程可以用许多方式实现,包括 softirq, tasklet, workqueue 等, 都属于实现中断上下部机制的实现。

softIRQ

softirq 定义了一些中断事件和处理函数,在中断的上半部中,如果 INTID 属于定义的软中断 之一, 则添加标志其处理函数需要被执行,只是标志,并不实际执行。

当中断服务程序退出之前,会遍历软中断列表中的状态,如果有需要处理的,则调用注册的处理函数。

注意,软中断处理函数的执行是在中断上下文中,用户进程只能等待中断完成才能有机会被调用, 所以软中断的一个问题是, 如果需要执行的处理函数过多,会导致一般线程长时间不能被调度。

同样地,因为处理函数的执行在中断上下文,所以也不能执行可能导致进程睡眠的操作, 例如申请锁,可能导致优先级反转。

tasklet

workqueue


创建于: 2023-04-13T23:51:49, Lastmod: 2024-05-12T21:54:13