如何为 AVR 实现编译时 [dispatch] table?

How to implement a compile-time [dispatch] table for AVR?

我的先决条件与 How can I implement a dynamic dispatch table in C 中的 Dave Durbin 相同...除了我的目标是 AVR。这是我的限制条件:

通常,table 应包含以下类型的项目:

typedef struct jump_item {
    uint16_t function_id;
    void (*callback)(void);
} jump_item_t;

我已经尝试按照答案中的建议使用自定义部分,但是 linker 会针对未知符号 __start_myownsection 抛出错误(无论我使用什么部分名称)。当然,因为代码的目标是 Linux/GCC。但我想我已经接近了,因为 avr-gcc 实际上 可以 使用 sections,只是我还没有弄清楚如何在用户定义的部分并实际指向 table 的开头,并在 运行 时确定 table 的长度。

Art's answer如何适配AVR?


* 编辑 *

我可以看到至少有两种方法可以使用部分来实现我想要的功能,或者使用函数 "attached" 到用户定义的部分,或者 tables 结构(如上定义)所有将堆叠在用户定义的部分中。我目前的问题是:

我更喜欢第二个选项,类似这样:

module1.c:

const jump_item_t module1_table[] __attribute__((__progmem__, section("tbl_dispatch"))) =
{
    { 0x02, func11 },
    { 0x03, func12 },
    ...
};

module2.c:

const jump_item_t module2_table[] __attribute__((__progmem__, section("tbl_dispatch"))) =
{
    { 0x12, func21 },
    { 0x13, func22 },
    ...
};

注意:索引不被视为相关。

当所有模块都定义了这样的变量时,它们会被优化掉,因为没有任何地方可以引用这些变量。不过,他们需要在 tbl_dispatch 部分堆叠起来。所以我的问题回到:

我如何告诉编译器删除它 "thinks" 未使用但仅用于特定 C/C++ 模块的变量?

目前我使用的全局命令行如下:

avr-gcc -g -Wall -mcall-prologues -fshort-enums -Os \
    -DF_CPU=8000000 -Wl,-relax -mmcu=... \
    *.cpp *.c -o main

* 编辑 *

令我失望的是,PROGMEM 和自定义部分不能放在一起。我试图将它们组合起来,但我在程序内存中得到了传播跳转 tables ... 当我完全包含这些内容时。事实上,并非所有 table 都出现在程序内存中。

放弃。

欢迎任何想法。

由于到目前为止我的所有尝试都失败了,所以我能想到的唯一实用方法是通过 makefile 脚本和菜单,就像构建 Linux 内核模块一样:您选择一系列模块进行编译make 脚本生成 header/source 个文件,调度 table.

构建生成的源文件以包含对所有必需函数和变量的引用,防止垃圾收集器在 link 时将它们撕掉。我没有实现的细节,这只是一个提示,虽然不是最简单的形式,但我可能会遵循。

如果您编写自己的自定义链接描述文件并复制为构造函数和析构函数(ctors 和 dtors)所做的工作,您绝对可以创建一个模块系统。下面的链接器脚本基于 AVR GCC 的 avr5.x,但我向其中添加了调度内容。

如果您在下面的 shell 会话中查看构建脚本的输出,您可以看到调度 table 已正确设置并且具有指向开始和结束的符号它。 shell 会话包括我用来编译此示例的所有源代码和构建脚本。

$ ls
avr5-x-modules.ld  build.sh  kernel.c  kernel.h  module_foo.c

$ cat avr5-x-modules.ld
/* Default linker script, for normal executables */
/* Copyright (C) 2014 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted in any medium without royalty provided the copyright
   notice and this notice are preserved.  */
OUTPUT_FORMAT("elf32-avr","elf32-avr","elf32-avr")
OUTPUT_ARCH(avr:5)
MEMORY
{
  text   (rx)   : ORIGIN = 0, LENGTH = 128K
  data   (rw!x) : ORIGIN = 0x800060, LENGTH = 0xffa0
  eeprom (rw!x) : ORIGIN = 0x810000, LENGTH = 64K
  fuse      (rw!x) : ORIGIN = 0x820000, LENGTH = 1K
  lock      (rw!x) : ORIGIN = 0x830000, LENGTH = 1K
  signature (rw!x) : ORIGIN = 0x840000, LENGTH = 1K
  user_signatures (rw!x) : ORIGIN = 0x850000, LENGTH = 1K
}
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  .hash          : { *(.hash)           }
  .dynsym        : { *(.dynsym)         }
  .dynstr        : { *(.dynstr)         }
  .gnu.version   : { *(.gnu.version)    }
  .gnu.version_d   : { *(.gnu.version_d)        }
  .gnu.version_r   : { *(.gnu.version_r)        }
  .rel.init      : { *(.rel.init)               }
  .rela.init     : { *(.rela.init)      }
  .rel.text      :
    {
      *(.rel.text)
      *(.rel.text.*)
      *(.rel.gnu.linkonce.t*)
    }
  .rela.text     :
    {
      *(.rela.text)
      *(.rela.text.*)
      *(.rela.gnu.linkonce.t*)
    }
  .rel.fini      : { *(.rel.fini)               }
  .rela.fini     : { *(.rela.fini)      }
  .rel.rodata    :
    {
      *(.rel.rodata)
      *(.rel.rodata.*)
      *(.rel.gnu.linkonce.r*)
    }
  .rela.rodata   :
    {
      *(.rela.rodata)
      *(.rela.rodata.*)
      *(.rela.gnu.linkonce.r*)
    }
  .rel.data      :
    {
      *(.rel.data)
      *(.rel.data.*)
      *(.rel.gnu.linkonce.d*)
    }
  .rela.data     :
    {
      *(.rela.data)
      *(.rela.data.*)
      *(.rela.gnu.linkonce.d*)
    }
  .rel.ctors     : { *(.rel.ctors)      }
  .rela.ctors    : { *(.rela.ctors)     }
  .rel.dtors     : { *(.rel.dtors)      }
  .rela.dtors    : { *(.rela.dtors)     }
  .rel.got       : { *(.rel.got)                }
  .rela.got      : { *(.rela.got)               }
  .rel.bss       : { *(.rel.bss)                }
  .rela.bss      : { *(.rela.bss)               }
  .rel.plt       : { *(.rel.plt)                }
  .rela.plt      : { *(.rela.plt)               }
  /* Internal text space or external memory.  */
  .text   :
  {
    *(.vectors)
    KEEP(*(.vectors))
    /* For data that needs to reside in the lower 64k of progmem.  */
     *(.progmem.gcc*)
    /* PR 13812: Placing the trampolines here gives a better chance
       that they will be in range of the code that uses them.  */
    . = ALIGN(2);
     __trampolines_start = . ;
    /* The jump trampolines for the 16-bit limited relocs will reside here.  */
    *(.trampolines)
     *(.trampolines*)
     __trampolines_end = . ;
     *(.progmem*)
    . = ALIGN(2);
    /* For future tablejump instruction arrays for 3 byte pc devices.
       We don't relax jump/call instructions within these sections.  */
    *(.jumptables)
     *(.jumptables*)
    /* For code that needs to reside in the lower 128k progmem.  */
    *(.lowtext)
     *(.lowtext*)
     __ctors_start = . ;
     *(.ctors)
     __ctors_end = . ;
     __dtors_start = . ;
     *(.dtors)
     __dtors_end = . ;
    KEEP(SORT(*)(.ctors))
    KEEP(SORT(*)(.dtors))
    __dispatch_start = . ;
    *(.dispatch)
    __dispatch_end = . ;
    KEEP(SORT(*)(.dispatch))
    /* From this point on, we don't bother about wether the insns are
       below or above the 16 bits boundary.  */
    *(.init0)  /* Start here after reset.  */
    KEEP (*(.init0))
    *(.init1)
    KEEP (*(.init1))
    *(.init2)  /* Clear __zero_reg__, set up stack pointer.  */
    KEEP (*(.init2))
    *(.init3)
    KEEP (*(.init3))
    *(.init4)  /* Initialize data and BSS.  */
    KEEP (*(.init4))
    *(.init5)
    KEEP (*(.init5))
    *(.init6)  /* C++ constructors.  */
    KEEP (*(.init6))
    *(.init7)
    KEEP (*(.init7))
    *(.init8)
    KEEP (*(.init8))
    *(.init9)  /* Call main().  */
    KEEP (*(.init9))
    *(.text)
    . = ALIGN(2);
     *(.text.*)
    . = ALIGN(2);
    *(.fini9)  /* _exit() starts here.  */
    KEEP (*(.fini9))
    *(.fini8)
    KEEP (*(.fini8))
    *(.fini7)
    KEEP (*(.fini7))
    *(.fini6)  /* C++ destructors.  */
    KEEP (*(.fini6))
    *(.fini5)
    KEEP (*(.fini5))
    *(.fini4)
    KEEP (*(.fini4))
    *(.fini3)
    KEEP (*(.fini3))
    *(.fini2)
    KEEP (*(.fini2))
    *(.fini1)
    KEEP (*(.fini1))
    *(.fini0)  /* Infinite loop after program termination.  */
    KEEP (*(.fini0))
     _etext = . ;
  }  > text
  .data          :
  {
     PROVIDE (__data_start = .) ;
    *(.data)
     *(.data*)
    *(.rodata)  /* We need to include .rodata here if gcc is used */
     *(.rodata*) /* with -fdata-sections.  */
    *(.gnu.linkonce.d*)
    . = ALIGN(2);
     _edata = . ;
     PROVIDE (__data_end = .) ;
  }  > data AT> text
  .bss  ADDR(.data) + SIZEOF (.data)   : AT (ADDR (.bss))
  {
     PROVIDE (__bss_start = .) ;
    *(.bss)
     *(.bss*)
    *(COMMON)
     PROVIDE (__bss_end = .) ;
  }  > data
   __data_load_start = LOADADDR(.data);
   __data_load_end = __data_load_start + SIZEOF(.data);
  /* Global data not cleared after reset.  */
  .noinit  ADDR(.bss) + SIZEOF (.bss)  :  AT (ADDR (.noinit))
  {
     PROVIDE (__noinit_start = .) ;
    *(.noinit*)
     PROVIDE (__noinit_end = .) ;
     _end = . ;
     PROVIDE (__heap_start = .) ;
  }  > data
  .eeprom  :
  {
    /* See .data above...  */
    KEEP(*(.eeprom*))
     __eeprom_end = . ;
  }  > eeprom
  .fuse  :
  {
    KEEP(*(.fuse))
    KEEP(*(.lfuse))
    KEEP(*(.hfuse))
    KEEP(*(.efuse))
  }  > fuse
  .lock  :
  {
    KEEP(*(.lock*))
  }  > lock
  .signature  :
  {
    KEEP(*(.signature*))
  }  > signature
  .user_signatures  :
  {
    KEEP(*(.user_signatures*))
  }  > user_signatures
  /* Stabs debugging sections.  */
  .stab 0 : { *(.stab) }
  .stabstr 0 : { *(.stabstr) }
  .stab.excl 0 : { *(.stab.excl) }
  .stab.exclstr 0 : { *(.stab.exclstr) }
  .stab.index 0 : { *(.stab.index) }
  .stab.indexstr 0 : { *(.stab.indexstr) }
  .comment 0 : { *(.comment) }
  .note.gnu.build-id : { *(.note.gnu.build-id) }
  /* DWARF debug sections.
     Symbols in the DWARF debugging sections are relative to the beginning
     of the section so we begin them at 0.  */
  /* DWARF 1 */
  .debug          0 : { *(.debug) }
  .line           0 : { *(.line) }
  /* GNU DWARF 1 extensions */
  .debug_srcinfo  0 : { *(.debug_srcinfo) }
  .debug_sfnames  0 : { *(.debug_sfnames) }
  /* DWARF 1.1 and DWARF 2 */
  .debug_aranges  0 : { *(.debug_aranges) }
  .debug_pubnames 0 : { *(.debug_pubnames) }
  /* DWARF 2 */
  .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
  .debug_abbrev   0 : { *(.debug_abbrev) }
  .debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end ) }
  .debug_frame    0 : { *(.debug_frame) }
  .debug_str      0 : { *(.debug_str) }
  .debug_loc      0 : { *(.debug_loc) }
  .debug_macinfo  0 : { *(.debug_macinfo) }
  /* SGI/MIPS DWARF 2 extensions */
  .debug_weaknames 0 : { *(.debug_weaknames) }
  .debug_funcnames 0 : { *(.debug_funcnames) }
  .debug_typenames 0 : { *(.debug_typenames) }
  .debug_varnames  0 : { *(.debug_varnames) }
  /* DWARF 3 */
  .debug_pubtypes 0 : { *(.debug_pubtypes) }
  .debug_ranges   0 : { *(.debug_ranges) }
  /* DWARF Extension.  */
  .debug_macro    0 : { *(.debug_macro) }
}

$ cat build.sh
CFLAGS="-std=gnu11 -mmcu=atmega328p"
set -uex
avr-gcc $CFLAGS -c module_foo.c -o module_foo.o
avr-gcc $CFLAGS -c kernel.c -o kernel.o
avr-gcc -T avr5-x-modules.ld kernel.o module_foo.o \
        -o program.elf -Wl,-Map=program.map
grep dispatch program.map

$ cat kernel.c
#include "kernel.h"
#include <avr/pgmspace.h>

extern dispatch_item * __dispatch_start;
extern dispatch_item * __dispatch_end;

int main()
{
    while (1)
    {
        for (dispatch_item * item = __dispatch_start; item < __dispatch_end; item++)
        {
            // TODO: Insert code here for reading the contents of the
            // dispatch item from program space and using it.  You
            // probably have to use pgm_read_word avr avr/pgmspace.h,
            // but with GCC 5 you could probably use the new named
            // memory space feature to just access the dispatch item
            // the same way you would access any other struct:
            // https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html
        }
    }
}

$ cat kernel.h
#pragma once

#include <stdint.h>

typedef struct dispatch_item {
    uint16_t func_id;
    void (*func)(void);
} dispatch_item;

#define DISPATCH_ITEM dispatch_item const __attribute__((section (".dispatch")))


$ cat module_foo.c
#include "kernel.h"
#include <avr/io.h>

// This gets called before main.
void __attribute__((constructor)) foo_init()
{
    PINB = 0;
}

// There is a pointer to this in the dispatch table.
void foo()
{
    PINB = 1;
}

// DISPATHCH_TABLE_ENTRY(0x12, &foo);

DISPATCH_ITEM foo_dispatch = { 0x12, &foo };

DISPATCH_ITEM foo_dispatch2 = { 0x13, &foo };

$ ./build.sh
++ avr-gcc -std=gnu11 -mmcu=atmega328p -c module_foo.c -o module_foo.o
++ avr-gcc -std=gnu11 -mmcu=atmega328p -c kernel.c -o kernel.o
++ avr-gcc -T avr5-x-modules.ld kernel.o module_foo.o -o program.elf -Wl,-Map=program.map
++ grep dispatch program.map
                0x00000002                __dispatch_start = .
 *(.dispatch)
 .dispatch      0x00000002        0x8 module_foo.o
                0x00000002                foo_dispatch
                0x00000006                foo_dispatch2
                0x0000000a                __dispatch_end = .
 SORT(*)(.dispatch)

自定义部分可以,但不要使用 PROGMEM。 使用 avr-gcc,PROGMEM 添加了一个 section 属性。 添加另一个会导致问题。 除非您努力,否则新部分将进入程序存储器。 您不需要替换默认的链接描述文件, 但您需要添加到它以获得新部分的开始和大小。 在ld手册中,参见3.10.9 Builtin Functions ADDR and SIZE, 3.11 隐式链接描述文件,3.5.4 源代码参考。