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

时间:2016-09-27 20:41:27

标签: compile-time avr-gcc

我和How can I implement a dynamic dispatch table in C中的Dave Durbin具有相同的先决条件......除了我的目标是AVR。以下是我的约束:

  • 模块将被列入一个列表,就像Linux编译内核模块一样
  • 编译时已知C(可以是C ++)模块的数量
  • 模块将静态链接(显然)
  • 我希望程序存储器中的表格not in SRAM

通常,该表应包含此类型的项目:

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

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

Art's answer如何适应AVR?

*编辑*

我可以看到至少两种方法来实现我想要的部分,或者使用功能"附加"到用户定义的一个或多个结构表(如上所定义),它们将堆叠在用户定义的部分中。我目前的问题是:

  • 在编译时优化了未使用的变量!
  • 由于链接器参数-gc-sections
  • 未使用的函数在链接时被优化掉,我需要清理未使用的函数。

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

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部分叠加。所以我的问题可以追溯到:

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

到目前为止,我使用的全局命令行如下:

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

*编辑*

令我失望的是, PROGMEM 和自定义栏目并没有结合在一起。我试图将它们组合在一起,但是我在程序存储器中得到了传播的跳转表...当我把它们包括在内时。事实并非所有表都出现在程序存储器中。

放弃。

任何的想法欢迎。

3 个答案:

答案 0 :(得分:1)

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

如果您在下面的shell会话中查看构建脚本的输出,您可以看到调度表已正确设置并且具有指向其开头和结尾的符号。 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)

答案 1 :(得分:0)

我能想到的唯一可行的方法,因为到目前为止我的所有尝试都失败了,就像通过makefile脚本和菜单一样,就像构建Linux内核模块一样:你选择了一系列模块来编译和{{1} }脚本使用调度表生成头文件/源文件。

生成的源文件是为了包含对所有必需函数和变量的引用,防止垃圾收集器在链接时将其删除。我没有详细的实现,这只是一个提示,我可能会遵循,虽然不是最简单的形式。

答案 2 :(得分:0)

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