Zephyr代码和数据重定位--1使用方法

Creative Commons
本作品采用知识共享署名

本文说明如何使用Zephyr的代码和数据重定位功能。

Zephyr目前仅提供静态重定位功能,即在编译期间按照用户的需求将指定文件中的 .text、.rodata、.data 和 .bss 重定位到所需的内存区域。该功能可以在不修改代码的情况下对内存内容重新排序。

基本概念介绍

在gcc的链接加载文件中通过MEMORY指定内存分区, 例如

1
2
3
4
5
6
7
8
MEMORY
{
FLASH (rx) : ORIGIN = (0x60000000 + 0x0), LENGTH = (8192*1K - 0x0)
RAM (wx) : ORIGIN = 0x80000000, LENGTH = (32768 * 1K)
ITCM : ORIGIN = 0, LENGTH = 131072
DTCM : ORIGIN = 0x20000000, LENGTH = 131072
OCRAM : ORIGIN = 0x20200000, LENGTH = 786432
}

上面是rt1062的一个内存分区结构:
FLASH: 外部8M大小的flash被映射到0x60000000地址,可以当作只读内存区,可执行代码。
RAM: 外部32M大小的SDRAM被映射到0x80000000地址,读写区域, 也可执行代码。
ITCM: rt1062片内内存指令区,大小128K被映射到0地址,可执行代码。
DTCM: rt1062片内内存数据区,大小128K被映射到0x20000000地址,数据读写区域。
OCRAM: rt1062片内内存区,大小768K被映射到0x20200000地址,数据读写区域,也可执行代码。
CPU对这几部分的访问速度为ITCM/DTCM>OCRAM>SDRAM>FLASH
由于ITCM/DTCM/ODRAM/SDRAM都是易丢性存储器,因此代码和数据通常会被保存到Flash上,上电后再由启动代码搬运到易丢失性存储器上。
XIP的默认情况下:.text, .rodata, .data, 都会被保存在Flash上,上电后启动代码会将.data搬运到SDRAM上,并根据Flash上.bss的信息在SRAM进行.bss初始化。
当我们希望.text在SDRAM/ITCM/OCRAM上更快的运行,一般需要bootloader来搬运,但如果只需要部分文件的代码在SDRAM/ITCM/OCRAM,或是.rodata, .data .bss被放到DTCM/OCRAM中读写更快,则可以通过链接脚本指定这些文件的段到特定的内存区域,并在启动代码中对其进行搬运即可。
例如,把一些特定的函数放到ITCM中,对linker.ld进行修改,加载地址在FLASH中,执行地址在ITCM中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 _ITCM_TEXT_SECTION_NAME :
{
. = ALIGN(4);
KEEP(*(.rel.text.strcat))
KEEP(*(.rel.text.strncat))
KEEP(*(.rel.text.strtok_r))
KEEP(*(.text.memchr))
KEEP(*(.text.memcmp))
KEEP(*(.text.memcpy))
KEEP(*(.text.memmove))
KEEP(*(.text.memset))
KEEP(*(.text.strcat))
KEEP(*(.text.strchr))
KEEP(*(.text.strcmp))
KEEP(*(.text.strcpy))
KEEP(*(.text.strlen))
KEEP(*(.text.strncat))
KEEP(*(.text.strncmp))
KEEP(*(.text.strncpy))
KEEP(*(.text.strnlen))
KEEP(*(.text.strrchr))
KEEP(*(.text.strtok_r))
. = ALIGN(4);
}
> ITCM AT > FLASH
__itcm_text_end = .;
__itcm_text_start = ADDR(_ITCM_TEXT_SECTION_NAME);
__itcm_text_size = SIZEOF(_ITCM_TEXT_SECTION_NAME);
__itcm_text_rom_start = LOADADDR(_ITCM_TEXT_SECTION_NAME);

在启动代码中添加对itcm的拷贝

1
2
z_early_memcpy(&__itcm_text_start, &__itcm_text_rom_start,
(size_t) &__itcm_text_size);

Zephyr的重定位就是将上面的动作通过脚本自动化。

使用说明

Zephyr要对文件做重定位非常容易,按照如下步骤即可

  1. 配置启用重定位功能,在prj.conf中加入

    1
    CONFIG_CODE_DATA_RELOCATION=y
  2. 使用zephyr_code_relocate将要重定位的文件加入CMakeLists.txt中:

    1
    2
    zephyr_code_relocate(FILES ${ZEPHYR_BASE}/lib/libc/minimal/source/string/string.c
    LOCATION ITCM_TEXT)

zephyr_code_relocate

zephyr_code_relocate是一个Zephyr定义的CMake函数,专门用于重定位, 支持的参赛有:

  • FILES: 一个文件列表,用于指定需要重定位的文件。
  • LIBRARY: 一个库名,用于指定需要重定位的库。
  • LOCATION: 一个字符串,指定重定位的目标内存区域,例如SRAM
  • 该函数还支持以下可选参数:
  • NOCOPY: 指示文件数据在启动时不需要被复制。
  • PHDR [program_header]: 添加程序头。在Xtensa平台上使用。
    示例:
    1
    2
    3
    4
    # 将file1.c和file2.c重定位到SRAM内存区内
    zephyr_code_relocate(FILES file1.c file2.c LOCATION SRAM)
    # 将my_lib.a重定位到SRAM内城区内
    zephyr_code_relocate(LIBRARY my_lib SRAM)

LOCATION

LOCATION<area>[_section]组成,area表示内存区,值的可选范围定义在MEMORY内。section表示被重定位目标那种段,有下面几种选择:

  • _TEXT .text代码段
  • _RODATA .rodata只读数据段
  • _DATA .data数据段
  • _BSS .bss未初始化数据段
  • _LITERAL .literal,给Xtensa用
    当未指定_section时表示所有段都将被重定位:
    1
    zephyr_code_relocate(FILES file2.c LOCATION SRAM)

只有代码段被重定位到SRAM中

1
zephyr_code_relocate(FILES file2.c LOCATION SRAM_TEXT)

代码段和数据段都被重定位到SRAM中

1
2
zephyr_code_relocate(FILES file2.c LOCATION SRAM_TEXT)
zephyr_code_relocate(FILES file2.c LOCATION SRAM_DATA)

也可写作

1
zephyr_code_relocate(FILES file2.c LOCATION SRAM_TEXT_DATA)

NOCOPY

所有被重定位的段都将发生拷贝,但如果代码段被重定位到Flash中并需要XIP执行,这种情况下我们不希望其被拷贝

1
zephyr_code_relocate(FILES src/ext_code.c LOCATION EXTFLASH_TEXT NOCOPY)

自定义内存分区

当zephyr的link.ld文件中的内存分区不能满足你的硬件或者设计时,需要添加自定义分区,自定义分区可以通过自定义link.ld来完成,例如自定义一个linker_arm_sram2.ld添加SRAM2区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <zephyr/linker/sections.h>
#include <zephyr/devicetree.h>

#include <zephyr/linker/linker-defs.h>
#include <zephyr/linker/linker-tool.h>


#if defined CONFIG_ARM
#define CONFIG_SRAM2 1
#define _SRAM2_DATA_SECTION_NAME .sram2_data
#define _SRAM2_BSS_SECTION_NAME .sram2_bss
#define _SRAM2_TEXT_SECTION_NAME .sram2_text
#define SRAM2_ADDR (CONFIG_SRAM_BASE_ADDRESS + RAM_SIZE2)
#endif

#define RAM_SIZE2 (CONFIG_SRAM_SIZE * 512)
MEMORY
{
#ifdef CONFIG_SRAM2
SRAM2 (wx) : ORIGIN = (CONFIG_SRAM_BASE_ADDRESS + RAM_SIZE2), LENGTH = RAM_SIZE2
#endif
}

#include <zephyr/arch/arm/aarch32/cortex_m/scripts/linker.ld>

通过include原本的linker.ld,加入新的MEMORY区域就可以完成,需要注意不同的soc有不同的zephyr定义linker.ld。另外需要特别注意的是由于zephyr重定位脚本的限制新加入的MEMORY分区名不能有下划线,否则重定位脚本解析会出错,错误示例: SRAM_2。
创建好文件后在prj.conf中指定即可

1
2
CONFIG_HAVE_CUSTOM_LINKER_SCRIPT=y
CONFIG_CUSTOM_LINKER_SCRIPT="linker_arm_sram2.ld"

之后按照前文方法指定你要重定位的文件段到SRAM2区域

1
2
zephyr_code_relocate(FILES src/test_file3.c LOCATION SRAM2_TEXT)
zephyr_code_relocate(LIBRARY test_lib LOCATION SRAM2)

其它

实例

zephyr提供的测试程序有比较全面的使用演示:https://github.com/zephyrproject-rtos/zephyr/tree/v3.3-branch/tests/application_development/code_relocation
另外示例代码中有nocopy的演示:https://github.com/zephyrproject-rtos/zephyr/tree/v3.3-branch/samples/application_development/code_relocation_nocopy

有效范围

Zephyr目前只有部分soc支持重定位,比较遗憾的是不支持esp32系列

  • Cortex-M 全系列
  • Cortex-A, Cortex-R 32bit全系列
  • 部分risc-v soc
    • it8xxx2
    • riscv-ite
    • gd32vf103
    • miv
    • mpfs
    • neorv32
    • opentitan
    • sifive-freedom
    • starfiv_jh71xx
    • telink_b91
    • virt
  • 部分xtensa soc
    • intel_adsp
    • sample_controller

对于默认不支持重定位的soc,可以采取自定义linker.ld的方式来做,再根据实际编译链接的情况来处理。

参考

https://docs.zephyrproject.org/latest/kernel/code-relocation.html