了解 ARM Cortex-M 中断向量 table 定义的 C 语法

Understanding this C syntax for ARM Cortex-M interrupt vector table definition

我目前正在查看其他人为 ARM Cortex-M 微控制器编写的一些启动代码。可以在 Github repo.

中找到整个文件

它会做一些事情,比如设置堆栈指针和初始化 .data.bss 部分,适当地使用一些简单的 for 循环。

我很难理解用于定义中断向量的语法table:

#define DUMMY __attribute__ ((weak, alias ("irq_handler_dummy")))

//-----------------------------------------------------------------------------
void irq_handler_reset(void);
DUMMY void irq_handler_nmi(void);
DUMMY void irq_handler_hard_fault(void);
// etc. 

extern int main(void);

extern void _stack_top(void);
// etc.

//-----------------------------------------------------------------------------
__attribute__ ((used, section(".vectors")))
void (* const vectors[])(void) =
{
  &_stack_top,                   // 0 - Initial Stack Pointer Value

  // Cortex-M0+ handlers
  irq_handler_reset,             // 1 - Reset
  irq_handler_nmi,               // 2 - NMI
  irq_handler_hard_fault,        // 3 - Hard Fault
  // etc.
};

GCC 的 __attribute__ 定义很清楚,我在官方文档中找到了它的作用的答案:GCC Function attributes.

我仍然不知道如何解析和表达这个语法的含义

void (* const vectors[])(void)

谁能帮我理解所有这些语法解包或表示什么?

vectors 是一个 const 函数指针数组,它接受 return void.

_stack_top 不是函数指针,它是堆栈顶部的地址,但对于皮质 m,它始终是向量 table 中的第一个元素。

Cortex M 架构和您正在使用的它的实现定义了向量的顺序和位置 table。此代码是生成 table 并将其放置在正确位置的语法糖。

void (* const vectors[])(void)

它是 const 函数指针数组 引用函数的定义和声明,该函数不带任何参数且 return 没有任何值。

它是通过引用源代码中定义的中断处理程序(函数)进行初始化的。第一个值采用链接描述文件中定义的符号 _stack_top 的地址。该值将在微启动时设置初始堆栈指针。

具有 weak 链接的函数可以被其他函数替换,而不会产生链接器错误。

向量table本质上只是一个ISR地址数组。其中翻译成C可以看作是一个函数指针数组。创建向量 table 作为函数指针数组是很常见的。

Cortex M 之所以成为一个特殊的雪花,是因为它通过硬件从闪存加载堆栈指针,而不是程序员在 运行 时间内手动设置它。向量的第一项 table 包含初始堆栈指针的值 - 它实际上 而不是 函数地址。因此,某种破解方式是必要的。 _stack_top 可能会归结为链接描述文件中设置的某个堆栈地址。您的代码永远不会直接使用此项,它就在那里,以便在启动时正确设置堆栈。

除了那个,其余的只是指向中断服务程序的普通函数指针。由于 ISR 不带参数且 returns 赋值,因此 ISR 函数指针的语法为:

void (*name) (void)

这样的函数指针数组声明为:

void (*name [n]) (void)

其中 n 可以选择性地用于表示数组大小。

__attribute__ ((used, section(".vectors"))) 只是将数组放在特定地址,在本例中是从 0 开始。您可以检查链接描述文件,您会在那里找到 .vectors

我们希望这个向量 table 作为只读数据加载到闪存 ROM 中。因此我们希望指针是只读的,而不是它们指向的内容。这是通过将 const 放在 * 的右侧来实现的(同样的规则也适用于普通对象指针):

void (*const vectors[])(void)

如果使用 typedef,我们可以写出更具可读性的代码:

typedef void isr_vector_t (void);
...

isr_vector_t* const vectors[] = { ... };
              vectors             -- vectors
              vectors[]           -- is an array of
      * const vectors[]           --   const pointer to
     (* const vectors[])(    )    --   function taking
     (* const vectors[])(void)    --     no parameters
void (* const vectors[])(void)    --   returning void

IOW,每个vectors[i]都是一个指向函数的指针;指向的函数在初始化程序中指定:

vectors[1] == irq_handler_reset,             // 1 - Reset
vectors[2] == irq_handler_nmi,               // 2 - NMI
vectprs[3] == irq_handler_hard_fault,        // 3 - Hard Fault

*后面的const表示vector[i]初始化后不能更新; IOW,您不能将 vectors[1] 设置为指向 irq_handler_reset 以外的函数。

const T *p;  // you can update p to point to different objects, but
             // you cannot write to the pointed-to objects

T const *p;  // same as above

T * const p = some_addr; // you can write a new value to the object at
                         // some_addr, but you can't write a new value
                         // to p.