使用为函数指针声明指定的“__irq”有什么作用吗?

Does using the `__irq` specified for a function pointer declaration do anything?

我必须为一个项目做一些嵌入式编程,并且正在通过查看其他一些项目来学习。我发现以下代码声明了向量 table:

typedef void (*const vect_t)(void) __irq;

vect_t vector_table[]
__attribute__ ((section("vectors"))) = {
  (vect_t) (RAM_BASE + RAM_SIZE),
  (vect_t) Reset_Handler,
  // ...
};

重置处理程序声明如下:

void Reset_Handler(void) {
  // ... no interesting
}

我阅读了 __irq,ARM 编译器文档说明如下:

The compiler generates function entry and exit sequences suitable for use in an interrupt handler when this attribute is present.

我猜 vect_t 应该是指向 void 函数的指针,它不带任何参数,即 suitable 用作中断处理程序。这对我来说似乎很奇怪,因为 __irq 应该只是实现的编译器提示,而不是对函数类型有贡献的东西(比如参数或 return type do)。

我的假设是 __irq 应该用在 Reset_Handler(以及所有其他中断处理程序)上,而不是在类型定义中。这是正确的吗?


请注意,我不是在问 __irq 做什么。我知道这不是 C 标准的一部分,而是 ARM 编译器扩展。我也明白使用它时产生的代码取决于CPU架构。

一般来说,中断服务例程 (ISR) 使用不同的指令进行 returning。普通函数只使用“return from subroutine”指令,该指令根据调用约定弹出堆栈。然而,ISR 不是由程序调用,而是由硬件调用,因此它们通常具有不同的调用约定。为了正确生成这些特殊指令,你需要一些非标准的中断语法。

该代码是一个中断向量table,所以类型定义是正确的。但是,如果 ISR 被声明为没有任何特殊关键字 void Reset_Handler(void) 的普通函数,那么这将不起作用。此处不正确的转换 (vect_t) Reset_Handler 将确保在中断时调用此函数,但它不会 return 从该函数正确地调用 - 可能会崩溃。

My assumption is that __irq should have been used on Reset_Handler (and on all other interrupt handlers) and not in the type definition. Is this correct?

它应该在向量 table 和 ISR 函数定义中。

例如使用 gcc(attributes/directives/pragmas 等特定于工具而不是 C 语言)

struct interrupt_frame;

__attribute__ ((interrupt))
void x (struct interrupt_frame *frame)
{
}

void y ( void )
{
}

使用通用的 aarch32 类型 arm 目标:

Disassembly of section .text:

00000000 <x>:
   0:   e25ef004    subs    pc, lr, #4

00000004 <y>:
   4:   e12fff1e    bx  lr

现在让我们进一步复杂化

struct interrupt_frame;

unsigned int k;

__attribute__ ((interrupt))
void x (struct interrupt_frame *frame)
{
    k=5;
}

void y ( void )
{
    k=5;
}

00000000 <x>:
   0:   e92d000c    push    {r2, r3}
   4:   e3a02005    mov r2, #5
   8:   e59f3008    ldr r3, [pc, #8]    ; 18 <x+0x18>
   c:   e5832000    str r2, [r3]
  10:   e8bd000c    pop {r2, r3}
  14:   e25ef004    subs    pc, lr, #4

0000001c <y>:
  1c:   e3a02005    mov r2, #5
  20:   e59f3004    ldr r3, [pc, #4]    ; 2c <y+0x10>
  24:   e5832000    str r2, [r3]
  28:   e12fff1e    bx  lr

对于中断,您需要保留中断中的所有寄存器,对于常规函数,调用约定规定了函数中哪些寄存器是可变的。因此,通过此示例,您可以看到该指令的主要原因,保留状态并使用中断指令中的特定 return。

因为 cortex-m 架构(armv6-m、7-m 和 8-m)的设计使您可以将 C 函数直接放在向量中 table,而不用任何 asm 环绕它们(硬件负责保存状态和特殊的 return 问题)。编译器以相同的方式生成代码,基本上该属性对该目标没有影响:

00000000 <x>:
   0:   2205        movs    r2, #5
   2:   4b01        ldr r3, [pc, #4]    ; (8 <x+0x8>)
   4:   601a        str r2, [r3, #0]
   6:   4770        bx  lr

0000000c <y>:
   c:   2205        movs    r2, #5
   e:   4b01        ldr r3, [pc, #4]    ; (14 <y+0x8>)
  10:   601a        str r2, [r3, #0]
  12:   4770        bx  lr

最后要注意的是,您不会 return 来自重置向量,因此 cortex-m 没有理由为重置向量使用这样的 attribute/directive。好吧,如果它确实是一个裸机向量 table,那么你不应该从重置向量中 return 架构(与使用相同的方案用于位于 os 上的一般应用程序入口相比,不是裸机-metal)(或调用此代码的引导加载程序,您当然可以return)。

其他架构不倾向于将重置集中在“中断”或“异常”列表中重置重置,ARM 文档和代码倾向于将它们视为任何其他异常,因此您仍然必须考虑它的不同。