在引导阶段将数据从闪存重定位到 RAM

Posted

技术标签:

【中文标题】在引导阶段将数据从闪存重定位到 RAM【英文标题】:Relocation of data from flash to RAM during boot phase 【发布时间】:2020-09-27 16:56:47 【问题描述】:

我目前正在尝试解决需要在启动阶段将数据从闪存移动到 RAM 的问题。现在一切都只是使用基于开源PULPissimo 的微控制器架构进行模拟。对于模拟,我使用 Mentor Graphics 的 QuestaSim。工具链是 GNU。

不幸的是,我对如何在启动阶段重新定位数据的经验几乎为零,所以我已经阅读了一些关于这个主题的帖子和教程,但我仍然对很多事情感到困惑。

情况如下:我将启动模式设置为从闪存启动,在这种情况下,这意味着代码已经预加载在闪存中。该代码实际上只是一个简单的 hello world 或任何其他程序。当我模拟所有内容并加载模块时。在引导阶段之后,将显示输出“hello world”并完成模拟。这意味着一切都按预期进行,这显然是一个好兆头和一个好的起点。

旁注:据我所知,PULPissimo 架构目前不支持从闪存直接启动,因此闪存中的数据必须移动到 RAM(他们称之为 L2)并执行。

据我了解,启动过程涉及多种事情。如果下一段有任何错误,请纠正我:

First:将要执行的代码。它是用 C 语言编写的,必须翻译成架构可以理解的语言。这应该自动完成并驻留在闪存预引导阶段。考虑到代码实际上是如上所述执行的,这里没有太多混乱。

第二:引导加载程序。这也是用 C 语言编写的。它也被翻译了,稍后将被烧录到 ROM 中,因此更改它没有多大意义。它加载启动所需的数据。它还可以区分您是要从闪存启动还是从 JTAG 启动。

第三:主启动文件crt0.S。这是让我感到困惑的事情之一,尤其是它究竟做了什么以及引导加载程序和主启动文件之间的区别是什么。***(是的,我知道...)将其定义为:“crt0(也称为 c0)是一组链接到 C 程序的执行启动例程,它在调用程序的主函数之前执行所需的任何初始化工作。”那么这是否意味着它与引导阶段无关,而是一种“初始化”和/或仅加载我想要执行的代码?

第四:链接描述文件link.ld。即使这是我读得最多的部分,仍然有很多问题。据我了解,链接描述文件包含有关重定位数据的位置的信息。要重定位的数据是我要执行的代码的数据(?)。它由 here 解释的不同部分组成。

.text program code;
.rodata read-only data;
.data read-write initialized data;
.bss read-write zero initialized data. 

有时我看到的不仅仅是这些部分,而不仅仅是文本、rodata、数据、bss。但是链接描述文件如何知道“文本”是什么,“数据”是什么等等?

我知道这对你们很多人来说是很多而且可能是非常基本的东西,但我真的很困惑。

我想要完成的是在启动阶段将数据从闪存重新定位到 RAM。不仅是我要执行的代码,还有更多位于闪存中的数据。考虑以下简单场景:我想运行一个 hello world C 程序。我想从闪存启动。到目前为止,没有什么特别的,一切正常。现在,在代码数据之后,我还将更多数据加载到闪存中,比如说 256 字节的 A(十六进制),所以我可以通过查找 AAAAAAA 部分来检查我在 QuestaSim 中的内存。我还想说明在启动阶段我希望将数据加载到哪里,例如 0x1C002000。我尝试使用 crt0.S 和 linker.ld 文件,但没有成功。它真正起作用的唯一一次是我修改了 bootloader.c 文件,但我必须假设它已经被烧录到 ROM 中,我无法对其进行任何修改。老实说,如果不对 bootloader.c 进行任何更改,我什至不确定我想要做的事情是否可行。

感谢您的宝贵时间。


更新


所以我玩了一下,并尝试创建一个简单的示例来了解正在发生的事情以及我可以进行哪些操作或重新定位。 首先,我创建了一个基本上只包含数据的 C 文件。 让我们称之为 my_test_data.c

  int normal_arr[] = 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555;

  int attribute_arr[] __attribute__ ((section(".my_test_section"))) = 0x66666666, 0x66666666, 0x66666666, 0x66666666, 0x66666666, 0x66666666, 0x66666666, 0x66666666;

  static int static_arr[] = 0x77777777, 0x77777777, 0x77777777, 0x77777777, 0x77777777, 0x77777777, 0x77777777, 0x77777777;

  int normal_var = 0xCCCCCCCC;

  static int static_var = 0xDDDDDDDD;

  int result_var;

然后我创建了目标文件。我通过 objdump 进行了查看,可以看到我的部分 my_test_section

  4 .my_test_section 00000020  00000000  00000000  00000054  2**2

之后,我尝试修改我的链接器脚本,以便将此部分加载到我指定的地址。这些是我在链接器脚本中添加的行(可能比需要的更多)。它不是整个链接器脚本!:

CUT01       : ORIGIN = 0x1c020000, LENGTH = 0x1000
     .my_test_section : 
        . = ALIGN(4);
        KEEP(*(.my_test_section))
        _smytest = .;
        *(.my_test_section)
        *(.my_test_section.*)
        _endmytest = .;
      > CUT01

我想看看 my_test_data.c 中的哪些数据被移动以及移动到哪里。请记住,我的目标是在启动后(或在启动期间,但您喜欢)将数据保存在 RAM 中(地址:0x1c020000)。不幸的是:

int normal_arr[] = 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555;

被移入 ROM(地址:0x1A000000),因为它似乎是链接描述文件已处理的 .text 部分 (iirc) 的一部分:

    .text : 
        . = ALIGN(4);
        KEEP(*(.vectors))
        _stext = .;
        *(.text)
        *(.text.*)
        _etext  =  .;
        *(.lit)
        ( ... more entries ...)
        _endtext = .;
      > ROM

让我感到困惑的是,我可以在上面的 .text 部分中添加这一行:

         *(.my_test_section)

然后来自 attribute_arr 的数据将位于 ROM 中,但如果我尝试将其移动到我添加的地址 (CUT01),则不会有任何结果。

我还生成了地图文件,其中还列出了 my_test_section。这是地图文件的摘录(不要介意输出文件在我的机器上的位置)。

.my_test_section
                0x000000001c020000       0x3c
                0x000000001c020000                _mts_start = .
 *(.text)
 *(.text.*)
 *(.comment)
 .comment       0x000000001c020000       0x1a /.../bootloader.o
                                         0x1b (size before relaxing)
 .comment       0x000000001c02001a       0x1b /.../my_test_data.o
 *(.comment.*)
 *(.rodata)
 *(.rodata.*)
 *(.data)
 *(.data.*)
 *(.my_test_section)
 *fill*         0x000000001c02001a        0x2 
 .my_test_section
                0x000000001c02001c       0x20 /.../my_test_data.o
                0x000000001c02001c                attribute_arr
 *(.my_test_section.*)
 *(.bss)
 *(.bss.*)
 *(.sbss)
 *(.sbss.*)
                0x000000001c02003c                . = ALIGN (0x4)
                0x000000001c02003c                _mts_end = .
OUTPUT(/.../bootloader elf32-littleriscv)

我将继续尝试让它工作,但现在我有点困惑,为什么 my_test_section 似乎被识别但没有移动到我指定的位置。这让我想知道是我在链接描述文件中犯了一个错误(或几个错误),还是其他文件之一(bootloader.c 或 crt0.S)可能是原因。

【问题讨论】:

我不熟悉你的环境,但是,根据环境,加载器等,我只是通过程序配置和加载器文件做了这样的事情,我不得不用在 main() 之前执行的汇编程序中的循环来做——那是代码,而不是数据;所有数据都由代码动态初始化,没有加载!所以很难给出一个普遍的答案:-) 也就是说,这可能不是我上面描述的原始环境。作为准则,定义为 const 的数据通常保存在只读存储器中。 bss 段中的数据未初始化——变量但没有存储数据。 .data 部分已初始化静态(包括全局、shhhhh...)变量——在一个不是异常原始的系统中,该数据应在程序开始时加载到 RAM 中。 我想你想同时问几个问题。也许如果你把它分解,问题会更容易回答。例如,您询问 crt0 以及它是否连接到引导阶段。您问“但是链接描述文件如何知道“文本”是什么以及“数据”是什么等等?等 你是对的,它同时是几个问题,但我认为这可能是有道理的,因为它们似乎彼此密切相关。让我首先关注 crt0.S。引用说:“crt0 是一组连接到 C 程序的执行启动例程,它在调用程序的主函数之前执行所需的任何初始化工作。”它还能用于将数据(除了程序本身)从闪存重新定位到 RAM 吗?程序本身不使用的数据?或者是否必须以某种方式包含附加数据,例如作为标题?因为这是我的主要目标。 我已经用我当前的状态更新了我的原始帖子。我觉得这有助于使事情保持清晰和可读。 【参考方案1】:

这里有很多问题。我将尝试回答部分问题。你问:

但是链接描述文件如何知道“文本”是什么以及 “数据”是等等?

附加、自定义、部分和预定义部分的处理方式不同。 自定义部分通常要求相关变量具有使用编译指示指定的部分。 标准部分由它们的类型定义:

文本:这是代码。这应该很清楚;对计算机的指示,而不是数据

rodata: const 数据——例如文字字符串(例如代码中的"This is a literal string"。一个好的编译器/链接器应该将定义为“const”(不是 const 参数)的变量放在rodata 部分也是如此。

bss:声明时未初始化的静态或全局变量:

int global_var_not_a_good_idea; // not in a function; local variables are different
static int anUninitializedArray[10];  

数据:声明时初始化的静态或全局变量

int initializedGlobalVarStillNotRecommended = 10;
static int initializedArray[] =  1, 2, 3, 4, 5, 6;

这个数据应该在程序加载时复制到内存中。

编辑: 在您的启动代码中的某处应该是一个重置处理程序。该函数将在处理器复位时调用。它应该是将数据复制到RAM,可能清除零段,初始化C库等的函数。完成初始化后,它应该调用main();

这是一个将数据重定位到 RAM 的示例(在本例中,来自 Atmel SAMG55 处理器的生成代码或示例代码,但思路应该相同)。 在链接描述文件内存空间定义中(我将省略实数): 内存(rwx):原点 = 0x########,长度 = 0x########

在链接描述文件部分定义中: .relocate : AT (_etext) . =对齐(4); _srelocate = .; (.ramfunc .ramfunc.); (.data .data.); . =对齐(4); _erlocate = .; > 内存

注意 _etext 是上一节的结尾

_srelocate 和 _erlocate 在启动代码中用于重定位所有文件中的 .data(显然还有 .ramfunc)中的所有内容:

/* 初始化重定位段 */ pSrc = &_etext; pDest = &_srelocate;

if (pSrc != pDest) 
        for (; pDest < &_erelocate;) 
                *pDest++ = *pSrc++;
        

这是一个非常标准的例子。如果您在项目中搜索调用 main() 的位置,您应该会找到类似的内容。

如果您只想将整个 .data 部分重新定位到您在 RAM 中指定的地址,您只需更改 RAM 部分位置的定义,而不是自己定义。如果您想将特定变量移动到不同的位置,您只需定义自己的部分

我不熟悉您正在使用的平台,但应该有一个 C 或汇编文件,其中包含运行 之前 crt0 的启动代码。这将设置堆栈、堆和中断向量。在某些实现中,此代码还将 .data 部分复制到 RAM,并且可以设置为将从数据部分的开头到 .bss 部分的开头的所有内容复制到 RAM。如果您的平台是以这种方式设置的,如果您将您的部分定位在 .data 和 .bss 之间,则应该复制它而无需您进行任何其他更改(例如,参见 here)。

但是,如果您想将数据复制到不同的位置,您可能需要添加代码来复制它,无论是在加载程序代码中还是在 main 的开头,使用您为开头定义的符号和本节的结尾。

不过,既然您提到它是只读数据,我建议您尽可能将其留在只读内存中。

【讨论】:

以上是关于在引导阶段将数据从闪存重定位到 RAM的主要内容,如果未能解决你的问题,请参考以下文章

代码重定位

代码重定位

SDRAM和重定位---icache的开启和关闭

使用 IAR EWARM 手动将 .data 部分从闪存复制到 ram

Eclipse:将 git 存储库从项目重定位到工作区

SDRAM和重定位---开始在汇编代码中调用 C 语言