FIQ和IRQ中断系统有什么区别?

84

我想了解在任何微处理器中(例如ARM926EJ),FIQ和IRQ中断系统之间的区别。

12个回答

173

ARM将FIQ称为快速中断,这意味着IRQ普通优先级。在任何实际系统中,除了两个设备外,还会有许多其他的中断源,因此会有一些外部硬件中断控制器,它允许对这些多个源进行屏蔽、优先级等控制,并驱动中断请求线到处理器。

在某种程度上,这使得两种中断模式之间的区别变得多余,许多系统根本不使用nFIQ,或者以与其他处理器上找到的非可屏蔽(NMI)中断类似的方式使用它(尽管在大多数ARM处理器上,FIQ是可以被软件屏蔽的)。

那么为什么ARM将FIQ称为“快速”?

  1. FIQ模式拥有自己专用的储存器组,包括寄存器r8-r14。R14是链接寄存器,保存从FIQ返回地址(+4)。但是,如果您的FIQ处理程序能够被编写成只使用r8-r13,它可以通过两种方式利用这些储存器组:
    • 一种方法是它不会产生由中断服务例程(ISR)使用的任何寄存器的推送和弹出开销。这可以在进入和退出ISR时节省大量周期。
    • 此外,处理程序可以依赖于来自一个调用到下一个调用中寄存器中持久存在的值,因此例如r8可以用作指向硬件设备的指针,处理程序可以信赖下一次被调用时r8中有相同的值。
  2. FIQ位置在异常向量表的末尾(0x1C),这意味着如果FIQ处理程序代码直接放置在向量表的末尾,则不需要分支-代码可以直接从0x1C执行。这可以在进入ISR时节省几个周期。
  • FIQ具有比IRQ更高的优先级。这意味着当核心接收到FIQ异常时,它会自动屏蔽IRQ。IRQ无法中断FIQ处理程序。相反,IRQ不会屏蔽FIQ,因此FIQ处理程序(如果使用)可以中断IRQ。此外,如果同时发生IRQ和FIQ请求,核心将首先处理FIQ。
  • 那为什么许多系统不使用FIQ?

  • FIQ处理程序代码通常不能用C语言编写-需要直接使用汇编语言编写。如果您足够关心中断服务例程的性能以想要使用FIQ,则在任何情况下都不希望使用C编码方式来节省一些周期,但更重要的是,C编译器不会生成遵循只使用寄存器 r8-r13 的限制的代码。符合ARM的 ATPCS过程调用标准的C编译器生成的代码将使用寄存器 r0-r3 作为临时值,并且不会在函数结尾处生成正确的cpsr还原返回代码。
  • 所有中断控制器硬件通常都在IRQ引脚上。仅在nFIQ输入连接了单个最高优先级中断源时,使用FIQ才有意义,而许多系统没有单个永久最高优先级源。将多个源连接到FIQ,然后通过软件按优先级排序是没有意义的,因为这几乎消除了FIQ相对于IRQ的所有优势。

  • 4
    FIQ 用于 ARM TrustZone 实现中的安全世界,以区分来自“安全”中断源的中断。确定哪些可能是安全中断源以及如何与普通中断有所不同地处理它取决于威胁和实现模型。 - divegeek
    1
    GCC和LLVM都缺乏FIQ代码所需的优化。它们将继续使用寄存器r0到r7,而不是r8及以上。因此,生成的代码会将它们推入/从堆栈中弹出。此外,在调用函数时,编译器将继续使用标准ABI(函数可能会改变r0到r3)。很快,生成的汇编代码就变得标准化且效率低下。 - Sven

    79

    FIQ或者快速中断,在一些ARM参考文献中被称为软DMA
    FIQ的特点包括:

    1. 独立模式,具有存储器区和堆栈、链接寄存器以及R8-R12。
    2. 独立的FIQ使能/禁用位。
    3. 向量表的尾部(始终在缓存中,并由MMU映射)。

    最后一个特点也比IRQ稍微具有一些优势,因为IRQ需要分支。

    'C'语言中的速度演示

    有人引用了使用汇编处理FIQ的编码难度。 gcc有注释来编写FIQ处理程序。以下是一个示例:

    void  __attribute__ ((interrupt ("FIQ"))) fiq_handler(void)
    {
        /* registers set previously by FIQ setup. */
        register volatile char *src asm ("r8");  /* A source buffer to transfer. */
        register char *uart asm ("r9");          /* pointer to uart tx register. */
        register int size asm ("r10");           /* Size of buffer remaining. */
        if(size--) {
            *uart = *src++;
        }
    }
    

    这将翻译成以下几乎良好的汇编代码:

    00000000 <fiq_handler>:
       0:   e35a0000        cmp     sl, #0
       4:   e52d3004        push    {r3}            ; use r11, r12, etc as scratch.
       8:   15d83000        ldrbne  r3, [r8]
       c:   15c93000        strbne  r3, [r9]
      10:   e49d3004        pop     {r3}            ; same thing.
      14:   e25ef004        subs    pc, lr, #4
    

    地址为0x1c的汇编程序可能如下所示:

       tst     r10, #0    ; counter zero?
       ldrbne  r11, [r8]  ; get character.
       subne   r10, #1    ; decrement count
       strbne  r11, [r9]  ; write to uart
       subs    pc, lr, #4 ; return from FIQ.
    

    一个真正的UART可能会有一个准备位,但用于实现高速软件DMA和FIQ的代码通常只需要10-20条指令。主要代码需要轮询FIQ r10 来确定缓冲区何时完成。主(非中断)代码可以通过使用msr指令切换到FIQ模式并将非分行的R0-R7转移到分行的R8-R13寄存器来传输和设置分行的FIQ寄存器。

    通常RTOS中断延迟为500-1000条指令。对于Linux, 可能是2000-10000条指令。实际DMA总是更可取的,但对于高频简单中断(如缓冲区传输),FIQ可以提供解决方案。

    由于FIQ是关于速度的,如果你不确定自己能否在汇编语言中编码(或愿意投入时间),则不应考虑它。由无限运行的程序员编写的汇编程序比编译器更快。使用GCC可以帮助新手。

    延迟

    由于FIQ有一个单独的掩码位,因此几乎普遍启用。在早期的ARM CPU(如ARM926EJ)中,一些原子操作必须通过屏蔽中断来实现。即使是最先进的Cortex CPU,仍有时候操作系统会屏蔽中断。通常服务时间对于中断并不重要,但信号和服务之间的时间很重要。在这里,FIQ也具有优势。

    弱点

    FIQ不可扩展。为了使用多个FIQ源,分行寄存器必须在中断例程之间共享。还必须添加代码以确定引起中断/FIQ的原因。 FIQ通常是一个“万能选手”。

    如果你的中断非常复杂(网络驱动程序、USB等),那么FIQ可能就没有意义了。这与多路复用中断的说法基本相同。分行寄存器提供了6个免费变量可供使用,它们不需要从内存中读取。寄存器比内存快。寄存器比L2缓存快。寄存器比L1缓存快。寄存器很快。如果你无法编写一个使用6个变量运行的例程,那么FIQ就不适合。注意:如果使用16位值,可以使用移位旋转来同时双重处理某些寄存器。

    显然,FIQ更为复杂。操作系统开发人员希望支持多个中断源。客户对FIQ的要求也各不相同,他们往往意识到应该让客户自己去实现。通常,对FIQ的支持是有限的,因为任何支持都可能削弱其主要优势——速度

    总结

    不要抨击我的朋友FIQ。它是系统程序员对付愚蠢硬件的一个技巧。它并不适用于每个人


    1
    作为补充说明,对于安全的世界操作系统(ARM TrustZone)来说,使用FIQ几乎是强制性的。然而,在这种情况下,它作为一个普通的中断处理程序,而不是传统的SoftDMA角色。正常世界使用正常的IRQ机制,而安全世界使用FIQ - artless noise
    只是想知道为什么你在处理程序中没有读取ACK并发送EOI。 - Charvak
    @Charvak 这是中断控制器特定的问题;你在考虑 GIC。一些中断控制器会自动清除(例如,当写入字符时,uart准备好状态会变为“未准备好”)。这只是一个假设性的例子(为了提供通用答案;OP使用的是ARM926),并不是一个实际的工作示例。如果你使用的是该控制器,请将 GIC 基址放入 FIQ 寄存器中,并执行 ACKEOI - artless noise
    本地寄存器变量并不像你想象的那样。它们不会为某个特定变量保留寄存器。事实上,只有在执行具有指定变量输入或输出参数的内联汇编时,gcc 才能保证变量的值在指定的寄存器中。在任何其他时间,寄存器可能被用于其他数据。特别是,我认为 gcc 不理解当函数返回时 src 必须在寄存器 r8 中。然而,全局寄存器变量似乎很适合这里,因为它们确实保留了寄存器。 - Sven
    @Sven 可能是正确的。我只是想提供汇编程序的框架。我可能会重试,看看是否可以让编译器生成实际的FIQ例程。然而,FIQ分支寄存器对于GCC来说并不是一个常见的概念,因此手动编码(特别是可能需要重新定位的代码)可能比通过解决所有细节来获取GCC生成适当的FIQ代码更有效。 - artless noise
    1
    众所周知,GCC倾向于使用r0到r7寄存器,即使r8和其他寄存器也可以使用。可惜的是,在我看来,GCC还不是编写FIQ处理程序的最佳选择。参考链接:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48429 - Sven

    66

    现代ARM CPU(和一些其他CPU)的一个特性。

    来自专利文件:

    提供了一种在数字数据处理器中执行快速中断的方法,该数字数据处理器具有处理多个中断的能力。当接收到快速中断请求时,将设置一个标志并将程序计数器和条件代码寄存器存储在堆栈上。在中断服务例程结束时,从中断返回指令检索包含数字数据处理器状态的条件代码寄存器,并检查标志是否已设置。如果设置了标志,则表示已服务于快速中断,因此只需取消堆栈上的程序计数器。

    换句话说,FIQ就是更高优先级的中断请求,在请求处理期间通过禁用IRQ和其他FIQ处理程序来优先处理。因此,在处理活动FIQ中断时不会发生其他任何中断。


    2
    不是贬低答案的意思,但专利中所述并不一定反映实际实现情况,因此我不认为它是权威参考。 - abc

    6
    混沌已经回答得很好了,但是迄今为止没有涉及到的一个额外的点是FIQ位于向量表的末尾,因此通常只需从那里开始例程,而IRQ向量通常只是那样(即跳转到其他地方)。在完全存储和上下文切换之后避免额外的分支是轻微的速度增益。

    5

    另一个原因是在FIQ情况下,只需要较少的寄存器推入堆栈,FIQ模式具有R8到R14_fiq寄存器。


    3

    FIQ是更高优先级的,可以在处理另一个IRQ时引入。 FIQ处理最关键的资源,其余资源由IRQ处理。


    1

    FIQ(快速中断请求)具有更高的优先级,毫无疑问,其他方面我不确定...... FIQ将支持高速数据传输或通道处理,在需要高速数据处理时我们使用FIQ,通常IRQ(普通中断请求)用于正常的中断处理。


    1

    0

    关于FIQ没有任何魔法。FIQ可以中断正在服务的任何其他IRQ,这就是为什么它被称为“快速”的原因。系统对这些中断的反应更快,但其余部分保持不变。


    0

    这取决于我们如何设计中断处理程序,因为FIQ是最后一个,它可能不需要一个分支指令,而且它有独特的r8-r14寄存器集,所以下一次我们回到FIQ中断时,我们不需要将栈推入/弹出。当然,它节省了一些周期,但再次,让更多的处理程序为一个FIQ服务并不明智,是的,FIQ的优先级更高,但这并不意味着它处理中断更快,IRQ/FIQ都在相同的CPU频率下运行,所以它们必须以相同的速度运行。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接