4

我想实现一种简单的方法来声明/定义应该在编译时添加到 R/O 内存中的函数指针表(特别是使用 GCC 的 AVR 设备上的程序闪存)的函数,同时还有一个默认函数指针被放置在所有未使用的条目中。例如,如果我有 32 个可能的条目,那么以下内容:

DEFAULTFUNC
void default_handler(...)
{
   ...
}

FUNC(28)
void handle_foo(...)
{
   ...
}

将指针放在handle_foo函数表的元素 28 中,同时放入default_handler其他 31。

我已经查看了 avr-libc 如何实现ISR()中断向量,但似乎它取决于一些我尚未找到的用于将函数指针放置在.vectors段内的内部 GCC 行为。我如何在代码中模仿它以在.{,rel{,a}.}rodata段中适当地创建函数指针表?

4

3 回答 3

2

http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html讨论了将变量放入特定的链接器部分。他们给出了一个示例,用于将 uart 结构放置在可能已配置为硬件地址(名为 DUART_A)的部分:

struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };

在您描述的情况下,我相信您会简单地命名现有的只读部分,以使表格放置在它在那里找到空间的任何地方。

于 2013-11-07T20:14:47.303 回答
1

您可能正在例如某些 Linux PC 上构建您的嵌入式软件(否则您应该这样做)。然后你可能会使用make或其他一些构建器。

然后你可以生成一个 C 文件(使用旧的m4GPP或你最喜欢的 Python 脚本、awk或其他...)。然后将该生成的文件(可能通过#include-ing 它的某处)提供给 C 编译器。

生成的 C 代码(可能是您的 C 宏调用序列)可以静态(在 C 编译时)构造您的只读函数表。

否则,扩展您的 GCC,例如使用MELT编码的一些定制来实现奇迹。在您的特定情况下,我认为这不值得(但我可能是错的),因为在您的情况下生成某些 C 代码的某些部分是如此简单......

于 2013-11-07T20:17:01.857 回答
1

我可能晚了几年,但是这个问题引起了我的兴趣,所以我用一点 C 代码、几个__attribute__s 和链接器对弱符号的支持来解决它(avr-libc对于向量表基本上是一样的,但是在汇编代码中)。

简单的最小示例在此答案中,但我已在https://github.com/ndim/handler-function-table提供了稍微扩展的版本。

处理程序表接口:

/* handler_table.h */
#ifndef HANDLER_TABLE_H
#define HANDLER_TABLE_H
#include <avr/pgmspace.h>
#define HANDLER_MAX 2
typedef void (*handler_func)(void);
extern const handler_func handler_table_P[HANDLER_MAX] PROGMEM;
#endif /* HANDLER_TABLE_H */

handler表的实际实现:

/* handler_table.c */
#include "handler_table.h"
void handler_foo(void) __attribute__((weak, alias("__handler_default")));
void handler_bar(void) __attribute__((weak, alias("__handler_default")));

const handler_func handler_talbe_P[HANDLER_MAX] PROGMEM = {
  handler_foo,
  handler_bar
};

__attribute__((weak))
void handler_default(void)
{
}

void __handler_default(void)
{
  handler_default();
}

测试用例主程序:

/* testcase-main.c */
#include "handler-table.h"

int main()
{
  for (unsigned int i=0; i<HANDLER_MAX; ++i) {
    const uint16_t func_addr = pgm_read_word_near(handler_table_P[i]);
    const handler_func func = (handler_func) addr;
    func();
  }
}

测试用例 2 定义了自己的处理程序:

/* testcase-2.c */
void handler_default(void)
{ ... }
void handler_foo(void)
{ ... }

链接两个测试用例:

  1. testcase-1: testcase-main.o handler-table.o

    仅使用默认处理程序。

  2. testcase-2: testcase-main.o handler-table.o testcase-2.o

    仅使用 testcase-2.c 提供的处理程序

几点说明:

  • 这是链接时实现,而不是编译时实现。

  • 这通过使用弱符号定义所有处理程序来工作,以便任何其他目标文件可以实现处理程序符号并因此覆盖弱符号。对于默认处理程序,我们使用一个虚拟处理程序,它只为默认处理程序调用弱符号。

    虚拟处理程序的堆栈帧和代码显然是浪费的,如果您需要避免循环和占用的空间,可以很容易地通过内联汇编中的跳转来替换。

  • 我没有在实际的 AVR MCU 上测试过这个,只是在我的 PC 上进行了测试。不过,我非常有信心正确使用PROGMEMand pgm_read_word_near

  • 如果您错误键入了已覆盖的处理程序函数的名称,则不会在没有任何编译时间或链接时间警告或错误的情况下将其输入处理程序表。

    我建议在预处理器宏中为处理函数的名称添加一层间接层,以便在出现拼写错误时得到编译时错误。

    对于需要从表中调用特定处理程序并因此需要知道其在表中的索引的代码,这个宏层可能也会派上用场。

    另一方面......在某些情况下,您可能一开始就不需要函数表,只有一些弱函数可能被非弱函数覆盖,就可以在没有表的情况下解决您的问题。

于 2017-08-14T16:47:21.540 回答