Zephyr 数据结构 Iterable Sections

概述 链接到标题

Iterable Sections 直译为可迭代段,在 Zephyr 中的作用是将相同数据结构的多条记录集中放在一个 section 内,方便遍历访问,在 Zephyr 的模块中大量使用这种机制,例如 shell, usb, 蓝牙,传感器等等。Iterable Sections 是利用链接脚本的特性完成,Zephyr 中通过 cmake 和头文件抽象出独立易用的接口,在不用修改 ld 文件的情况下可以很方便的加入 Iterable Sections。

通常我们想要实现一个 Iterable Section 会通过下面几步来完成:

  1. 修改 linker.ld 加入 Section
iterables_my_struct_data_area :
{
    _iterables_my_struct_data_list_start = .;
    KEEP ( * ( SORT_BY_NAME ( ._iterables_my_struct_data.static.* )) ) ;
    _iterables_my_struct_data_list_end = .;
} > dram0_0_seg AT > ROM
  1. 在源代码将需要放到_iterables 的变量进行定义
struct my_struct_data{
    int type;
    char *en_str;
    char *zh_str;
}

struct my_struct_data volup __attribute__ (( section ( "._iterables_my_struct_data.static" )) ) = {VOL_UP, "VOL+", "音量加"};
struct my_struct_data voldown __attribute__ (( section ( "._iterables_my_struct_data.static" )) ) = {VOL_DOWN, "VOL-", "音量减"};
struct my_struct_data ok __attribute__ (( section ( "._iterables_my_struct_data.static" )) ) = {VOL_OK, "OK", "确认"};
struct my_struct_data cancel __attribute__ (( section ( "._iterables_my_struct_data.static" )) ) = {VOL_CANCEL, "CANCEL", "放弃"};
  1. 遍历整个区域,读取数据
char get_str ( int type, int lang )
{
    for ( struct my_struct_data* iter = _iterables_list_start;
            iter<_iterables_list_end;
            iter++ ) {
        if ( iter->type == type ) {
            if ( lang == LANG_ZH ) {
                return iter->zh_str;
            }else{
                return iter->en_str;
            }
        }
    }

    return NULL;
}

在 Zephyr 中将上面的过程进行抽象,在 zephyr/include/zephyr/linker/iterable_sections.h 中提供一组宏,搭配 zephyr cmake 函数 zephyr_linker_sources 完成上面的第 1 步,修改 linker.ld。在 zephyr/include/zephyr/sys/iterable_sections.h 中提供一组宏,完成上面的 2,3 步。

使用 链接到标题

示例 链接到标题

这个示例演示如何使用 Zephyr 的达到上面的目的

  1. 使用 ITERABLE_SECTION_RAM 添加 section 新建文件 iter.ld,内容如下
ITERABLE_SECTION_RAM ( my_struct_data, 4 )

修改 app 目录下的 CMakeList.txt,添加如下内容

zephyr_linker_sources ( CONFIG_INTER_SAMPLE DATA_SECTIONS iter.ld )
  1. 使用 STRUCT_SECTION_ITERABLE 添加数据项
struct my_struct_data{
    int type;
    char *en_str;
    char *zh_str;
}

# define DEFINE_ITERABLE_RAM_DATA ( name, _type, _en, _zh ) \
         STRUCT_SECTION_ITERABLE ( my_struct_data, name ) = { \
                .type = _type,\
                .en_str = _en, \
                .zh_str = _zh, \
         }

DEFINE_ITERABLE_RAM_DATA ( volup, VOL_UP, "VOL+", "音量加" ) ;
DEFINE_ITERABLE_RAM_DATA ( voldown, VOL_DOWN, "VOL-", "音量减" ) ;
DEFINE_ITERABLE_RAM_DATA ( ok, VOL_OK, "OK", "确认" ) ;
DEFINE_ITERABLE_RAM_DATA ( cancel, VOL_CANCEL, "CANCEL", "放弃" ) ;
  1. STRUCT_SECTION_FOREACH 遍历整个区域,读取数据
char get_str ( int type, int lang )
{
    STRUCT_SECTION_FOREACH ( my_struct_data, iter ) {
        if ( iter->type == type ) {
            if ( lang == LANG_ZH ) {
                return iter->zh_str;
            }else{
                return iter->en_str;
            }
        }
    }

    return NULL;
}

说明 链接到标题

链接宏 链接到标题

我们放置数据到 section 时,根据运行时是否需要改变会选择放 RAM 还是放 ROM,zephyr/include/zephyr/link/iterable_sections.h 提供不同的宏建立不同的 section。 对于要放到 RAM 内的使用下面一组宏

ITERABLE_SECTION_RAM ( struct_type, subalign )
ITERABLE_SECTION_RAM_NUMERIC ( struct_type, subalign )
ITERABLE_SECTION_RAM_GC_ALLOWED ( struct_type, subalign )

例如我们建立文件 iterables_ram.ld 内容如下,表示 struct struct_type_data_ram 以 4 字节对齐放置到 section 内:

ITERABLE_SECTION_RAM ( struct_type_data_ram, 4 )

相应的,其对应的文件需要在 CMakeList.txt 中使用 zephyr_linker_sources 时参数要指定为 DATA_SECTIONS 表示放入 RAM:

zephyr_linker_sources ( DATA_SECTIONS iterables_ram.ld )

对于要放到 ROM 内的使用下面一组宏

ITERABLE_SECTION_ROM ( struct_type, subalign )
ITERABLE_SECTION_ROM_NUMERIC ( struct_type, subalign )
ITERABLE_SECTION_ROM_GC_ALLOWED ( struct_type, subalign )

例如我们建立文件 iterables_ram.ld 内容如下,表示 struct struct_type_data_rom 以 4 字节对齐放置到 section 内:

ITERABLE_SECTION_ROM ( struct_type_data_rom, 4 )

相应的,其对应的文件需要在 CMakeList.txt 中使用 zephyr_linker_sources 时参数要指定为 SECTIONS 表示放入 RoM:

zephyr_linker_sources ( SECTIONS iterables_rom.ld )

结构放置顺序 链接到标题

前面看到 RAM/ROM 的链接宏都有三种,其对应的作用是在链接的时候指定结构体变量的排序和是否自动移除: **ITERABLE_SECTION_RAM/ITERABLE_SECTION_ROM **按照变量名字母顺序进行排序,例如前面 volup/voldown/ok/cancel 在 section 中的顺序就是:

cancel
ok
voldown
volup

ITERABLE_SECTION_RAM_NUMERIC/ITERABLE_SECTION_ROM_NUMERIC 按照编译时定义的顺序进行排序,例如前面 volup/voldown/ok/cancel 在 section 中的顺序就是:

volup
voldown
ok
cancel

ITERABLE_SECTION_RAM_GC_ALLOWED/ITERABLE_SECTION_ROM_GC_ALLOWED 在执行链接时只会保留被显示引用数据结构,其它的都会被链接优化掉。使用这两个宏要特别注意,例如下面的代码 STRUCT_SECTION_FOREACH 不会打印出任何东西:

STRUCT_SECTION_FOREACH ( my_struct_data, iter ) {
        if ( iter->type == type ) {
            if ( lang == LANG_ZH ) {
                return iter->zh_str;
            }else{
                return iter->en_str;
            }
        }
        printk ( "%d:%s-%s\r\n",iter->type, iter->zh_str, iter->en_str ) ;
    }

C 操作宏 链接到标题

在 C 代码中需要定义,访问 Iterable Sections 内的数据结构,这部分工作由 zephyr/include/zephyr/sys/iterable_sections.h 提供的宏来完成

//定义一个新元素 varname 到段 struct_type 中
STRUCT_SECTION_ITERABLE ( struct_type, varname )

//定义一组个数为 size 的新元素 varname 到段 struct_type 中,section 中符号的名称为 varname
STRUCT_SECTION_ITERABLE_ARRAY ( struct_type, varname, size )

//定义一个名称为 name 的元素 varname 到段 struct_type 中,section 中符号的名称为 name
STRUCT_SECTION_ITERABLE_NAMED ( struct_type, name, varname )

//遍历段 struct_type,每次遍历的结果都暂存在 iterator 中
STRUCT_SECTION_FOREACH ( struct_type, iterator )

//获取段 struct_type 中第 i 个元素的地址,将其放到 dst 指向的内存中
STRUCT_SECTION_GET ( struct_type, i, dst )

//获取段 struct_type 中元素的个数,放在 dst 指向的内存中
STRUCT_SECTION_COUNT ( struct_type, dst )

//获取段 struct_type 开始的位置
STRUCT_SECTION_START ( struct_type )

//外部引用段 struct_type 开始的位置
STRUCT_SECTION_START_EXTERN ( struct_type )

//获取段 struct_type 结束的位置
STRUCT_SECTION_END ( struct_type )

//外部引用段 struct_type 结束的位置
STRUCT_SECTION_END_EXTERN ( struct_type )

在连接文件中放一个段 sample_data, 对应的访问示例: 链接文件

ITERABLE_SECTION_RAM ( sample_data, 4 )

代码操作

struct sample_data{
    int type;
};

STRUCT_SECTION_ITERABLE ( sample_data, va ) = {1};
STRUCT_SECTION_ITERABLE_ARRAY ( sample_data, vb, 3 ) = {{2},{3},{4}};
STRUCT_SECTION_ITERABLE_NAMED ( sample_data, realname, vc ) = {5};

STRUCT_SECTION_FOREACH ( sample_data, iter ) {
    printk ( "iter->type %d\r\n", iter->type ) ;
}

struct sample_data *temp;
STRUCT_SECTION_GET ( sample_data, 3, &temp ) ;
printk ( "temp->type %d\r\n", temp->type ) ;

uint32_t count = 0;

STRUCT_SECTION_COUNT ( sample_data, &count ) ;
printk ( "Total %u\r\n", count ) ;

printk ( "Section %p~%p\r\n", STRUCT_SECTION_START ( sample_data ) , STRUCT_SECTION_END ( sample_data )) ;

得到的结果为:

iter->type 5
iter->type 1
iter->type 2
iter->type 3
iter->type 4
temp->type 3
Total 5
Section 0x3fc96b9c~0x3fc96bb0

由于 STRUCT_SECTION_ITERABLE_NAMED 会使用 realname 作为符号名称,按名称排序后对应的元素被放到了最开始。

原理简介 链接到标题

Zephyr 其本质就是利用链接的 section 属性来实现,原理比较简单, 这里不展开说明,只做简要分析

链接宏 链接到标题

定义在 zephyr/include/zephyr/linker/iterable_sections.h 内,前面列举的 6 个 ITERABLE_SECTION_ 宏最后都由下面的 Z_LINK_ITERABLE 宏实现

//ITERABLE_SECTION_ROM/ITERABLE_SECTION_RAM 使用
//用 KEEP 不会被链接优化
//用 SORT_BY_NAME 按名称进行排序
# define Z_LINK_ITERABLE ( struct_type ) \
    _CONCAT ( _##struct_type, _list_start ) = .; \
    KEEP ( * ( SORT_BY_NAME ( ._##struct_type.static.* )) ) ; \
    _CONCAT ( _##struct_type, _list_end ) = .

//ITERABLE_SECTION_ROM_NUMERIC/ITERABLE_SECTION_RAM_NUMERIC 使用
//用 KEEP 不会被链接优化
//用 SORT 按编译的顺序进行排序
# define Z_LINK_ITERABLE_NUMERIC ( struct_type ) \
    _CONCAT ( _##struct_type, _list_start ) = .; \
    KEEP ( * ( SORT ( ._##struct_type.static.*_?_* )) ) ; \
    KEEP ( * ( SORT ( ._##struct_type.static.*_??_* )) ) ; \
    _CONCAT ( _##struct_type, _list_end ) = .

//ITERABLE_SECTION_RAM_GC_ALLOWED/ITERABLE_SECTION_ROM_GC_ALLOWED 使用
//没有指定 KEEP,只要代码中没被引用的都会被链接优化掉
//用 SORT_BY_NAME 按名称进行排序
# define Z_LINK_ITERABLE_GC_ALLOWED ( struct_type ) \
    _CONCAT ( _##struct_type, _list_start ) = .; \
    -   ( SORT_BY_NAME ( ._##struct_type.static.* )) ; \
    _CONCAT ( _##struct_type, _list_end ) = .

Zephyr 为了方便使用者,其实际的 section name 用 struct_type 来生成,代码中引用也是通过 C 宏对 struct_type 进行操作。实际的 section name 对用户外并不开放,对于 ITERABLE_SECTION_RAM ( sample_data, 4 ) 经过转换后在链接脚本中的实际形式如下:

sample_data_area : ALIGN_WITH_INPUT SUBALIGN ( 4 )
{
    _sample_data_list_start = .;
    KEEP ( * ( SORT_BY_NAME ( ._sample_data.static.* )) ) ;
    _sample_data_list_end = .;
} > dram0_0_seg AT > ROM

C 操作宏 链接到标题

定义在 zephyr/include/zephyr/sys/iterable_sections.h 内,前面列举的一组 STRUCT_SECTION_ 宏最后都由下面的 TYPE_SECTION_ 宏实现

//所有定义变量到 section 的宏都由这个宏封装而来
//通过__attribute__将变量放到指定的 section 中
# define TYPE_SECTION_ITERABLE ( type, varname, secname, section_postfix ) \
    Z_DECL_ALIGN ( type ) varname \
    __in_section ( _##secname, static, _CONCAT ( section_postfix, _ ))__used __noasan

//section 开始地址
# define TYPE_SECTION_START ( secname )_CONCAT ( _##secname, _list_start )

//section 结束地址
# define TYPE_SECTION_END ( secname )_CONCAT ( _##secname, _list_end )

//外部声明 section 开始地址
# define TYPE_SECTION_START_EXTERN ( type, secname ) \
    extern type TYPE_SECTION_START ( secname ) []

//外部声明 section 结束地址
# define TYPE_SECTION_END_EXTERN ( type, secname ) \
    extern type TYPE_SECTION_END ( secname ) []

//通过 for 循环,从 section 开始地址变量到 section 结束地址
# define TYPE_SECTION_FOREACH ( type, secname, iterator ) \
    TYPE_SECTION_START_EXTERN ( type, secname ) ;        \
    TYPE_SECTION_END_EXTERN ( type, secname ) ;        \
    for ( type * iterator = TYPE_SECTION_START ( secname ) ; ({    \
        __ASSERT ( iterator <= TYPE_SECTION_END ( secname ) ,\
                  "unexpected list end location" ) ;    \
             iterator < TYPE_SECTION_END ( secname ) ;    \
 }) ;                        \
         iterator++ )

//通过数组下标的方式,获取 section 中指定索引元素的地址
# define TYPE_SECTION_GET ( type, secname, i, dst ) do { \
    TYPE_SECTION_START_EXTERN ( type, secname ) ; \
    -   ( dst ) = &TYPE_SECTION_START ( secname ) [i]; \
} while ( 0 )

//计算 section 中元素的个数
# define TYPE_SECTION_COUNT ( type, secname, dst ) do { \
    TYPE_SECTION_START_EXTERN ( type, secname ) ; \
    TYPE_SECTION_END_EXTERN ( type, secname ) ; \
    -   ( dst ) = (( uintptr_t ) TYPE_SECTION_END ( secname ) - \
 ( uintptr_t ) TYPE_SECTION_START ( secname )) / sizeof ( type ) ; \
} while ( 0 )

___in_section 是一个定义在 zephyr/include/zephyr/toolchain/gcc.h 的宏,用于生成 section 属性将变量可以指定到对应 section 中

# define ___in_section ( a, b, c ) \
    __attribute__ (( section ( "." Z_STRINGIFY ( a ) \
                "." Z_STRINGIFY ( b ) \
                "." Z_STRINGIFY ( c )) ))
# define __in_section ( a, b, c )___in_section ( a, b, c )

# define __in_section_unique ( seg )___in_section ( seg, __FILE__, __COUNTER__ )

# define __in_section_unique_named ( seg, name ) \
    ___in_section ( seg, __FILE__, name )

对于前面示例的:

STRUCT_SECTION_ITERABLE ( sample_data, va ) = {1};
STRUCT_SECTION_ITERABLE_ARRAY ( sample_data, vb, 3 ) = {{2},{3},{4}};
STRUCT_SECTION_ITERABLE_NAMED ( sample_data, realname, vc ) = {5};

最后放置到 section 中的符号和地址如下:

sample_data_area
                0x000000003fc96b9c       0x14 load address 0x0000000000010110
                0x000000003fc96b9c                _sample_data_list_start = .
 -   ( SORT_BY_NAME ( SORT_BY_ALIGNMENT ( ._sample_data.static.* )) )
 ._sample_data.static.realname_
                0x000000003fc96b9c        0x4 app/libapp.a ( iterables_sample.c.obj )
                0x000000003fc96b9c                vc
 ._sample_data.static.va_
                0x000000003fc96ba0        0x4 app/libapp.a ( iterables_sample.c.obj )
                0x000000003fc96ba0                va
 ._sample_data.static.vb_
                0x000000003fc96ba4        0xc app/libapp.a ( iterables_sample.c.obj )
                0x000000003fc96ba4                vb
                0x000000003fc96bb0                _sample_data_list_end = .

后面相应的访问:

//STRUCT_SECTION_FOREACH ( sample_data, iter ) {
//    printk ( "iter->type %d\r\n", iter->type ) ;
//}
for ( struct sample_data * iter = _sample_data_list_start;
        iter<_sample_data_list_end;
        iter++ ) {
    printk ( "iter->type %d\r\n", iter->type ) ;
}

struct sample_data *temp;

//STRUCT_SECTION_GET ( sample_data, 3, &temp ) ;
extern struct sample_data _sample_data_list_start[];
*temp = &_sample_data_list_start [3];

printk ( "temp->type %d\r\n", temp->type ) ;

uint32_t count = 0;

//STRUCT_SECTION_COUNT ( sample_data, &count ) ;
extern struct sample_data _sample_data_list_start[];
extern struct sample_data _sample_data_list_end[];
-   ( dst ) = (( uintptr_t )_sample_data_list_start - \
 ( uintptr_t )_sample_data_list_end ) / sizeof ( struct sample_data ) ; \

printk ( "Total %u\r\n", count ) ;

printk ( "Section %p~%p\r\n", _sample_data_list_start, _sample_data_list_end ) ;
//printk ( "Section %p~%p\r\n", STRUCT_SECTION_START ( sample_data ) , STRUCT_SECTION_END ( sample_data )) ;

参考 链接到标题

https://docs.zephyrproject.org/3.5.0/kernel/iterable_sections/index.html