数据段初始化器是什么?

3
我正在为运行在STM32F3Discovery开发板上的裸机应用构建链接脚本。它使用位于STM32Cube_FW_F3包中的CMSIS驱动程序的启动代码,准确地说是stm32f303xc.s文件。
上述文件(以下是一部分),引用了_sidata:
/* start address for the initialization values of the .data section.
defined in linker script */
.word   _sidata
/* start address for the .data section. defined in linker script */
.word   _sdata
/* end address for the .data section. defined in linker script */
.word   _edata
/* start address for the .bss section. defined in linker script */
.word   _sbss
/* end address for the .bss section. defined in linker script */
.word   _ebss

关于数据和bss段的起始和结束的引用是不言自明的,但是我无法找到有关数据段初始化器的任何信息。它会在复位后直接在SP设置之后使用。

stm32f303xc.s

    .section    .text.Reset_Handler
    .weak   Reset_Handler
    .type   Reset_Handler, %function
Reset_Handler:
  ldr   sp, =_estack    /* Atollic update: set stack pointer */

/* Copy the data segment initializers from flash to SRAM */
  movs  r1, #0
  b LoopCopyDataInit

CopyDataInit:
    ldr r3, =_sidata
    ldr r3, [r3, r1]
    str r3, [r0, r1]
    adds    r1, r1, #4

LoopCopyDataInit:
    ldr r0, =_sdata
    ldr r3, =_edata
    adds    r2, r0, r1
    cmp r2, r3
    bcc CopyDataInit
    ldr r2, =_sbss
    b   LoopFillZerobss
/* Zero fill the bss segment. */
FillZerobss:
    movs    r3, #0
    str r3, [r2], #4

LoopFillZerobss:
    ldr r3, = _ebss
    cmp r2, r3
    bcc FillZerobss

/* Call the clock system intitialization function.*/
    bl  SystemInit
/* Call static constructors */
    bl __libc_init_array
/* Call the application's entry point.*/
    bl  main
< p > _sidata应该指向哪个内存片段,它与数据段有什么关系?

2个回答

3
答案可以在GNU链接器手册的VMA和LMA主题下找到,它们表示“虚拟内存地址”和“加载内存地址”。对于初始数据(非零),我们需要一个副本进行初始化。通过以下节,在链接器脚本中将其放置在闪存中:
/*用于启动以初始化数据*/ _sidata = LOADADDR(.data);
/*已初始化的数据部分进入“RAM”Ram类型内存*/ .data: { .= ALIGN(4); / *对齐。* / _sdata = .; / *在数据开始处创建全局符号* / *(.data)/ * .data部分* / *(.data *)/ * .data *部分* /
.= ALIGN(4); / *对齐。* / _edata = .; / *在数据结尾处定义全局符号* / } > RAM AT> FLASH "AT> FLASH" 指的是将初始数据放置在FLASH部分,而LOADADDR()是获取此地址(LMA)的函数。该部分被放置在>RAM中,以便所有代码引用这些变量将被修正为使用“工作”地址(术语VMA)。 _sidata_sdata_edata都是链接器文件中声明的变量。它们可以作为'C'或汇编代码中的地址使用。
“另一方面,我找不到有关数据段初始化程序的任何信息。”
希望上述内容已经解释清楚了。同时,“RAM”版本的链接器文件包括这些变量并将数据复制到自身。因此,STM32的作者似乎也感到困惑。"

这段代码非常可疑。让我们从以下开始,

 .word   _sidata

这是为使用全局地址_sidata的数据腾出空间。它存在于链接器命令文件中。真正的用法应该是.extern _sidata,但这是默认值。这个文件的整个前导部分都没有作用?
/* Copy the data segment initializers from flash to SRAM */  
  movs  r1, #0         ; r1 is a counter up to size. 
  b  LoopCopyDataInit
CopyDataInit:
  ldr  r3, =_sidata    ; reload flash pointer
  ldr  r3, [r3, r1]    ; add count to flash pointer and get value
  str  r3, [r0, r1]    ; store value to RAM
  adds  r1, r1, #4     ; increment counter
    
LoopCopyDataInit:
  ldr  r0, =_sdata     ; (re)load destination
  ldr  r3, =_edata     ; (re)load end destination
  adds  r2, r0, r1     ; add count to start
  cmp  r2, r3          ; are we at end?
  bcc  CopyDataInit    ; loop.
 

这段代码非常低效且复杂;循环体有九个指令且不断重新计算值。它有次内存访问。 GNU 链接器手册提供了一个在'C'中执行此操作的公式,相同的符号可以轻松地在GNU汇编程序中使用。
.extern _sidata   /* Source of init data in flash. */
.extern _sdata    /* Target/start of init data in RAM. */
.extern _edata    /* End of init data in RAM. */

ldr r0, =_sidata
ldr r1, =_sdata
ldr r2, =_edata

/** Validate parameters. */
cmp   r1, r2       /* Zero size */
it    ne
cmpne r0, r1       /* Src is dest */
beq   2f           /* Skip it. */

1: /* init data copy loop */
ldr r3, [r0], #4  /* Load from flash and update source pointer. */
str r3, [r1], #4  /* Store to RAM and update dest pointer. */
cmp r1, r2
blo 1b
2:                /* exit */

很容易看出,一些填充和对齐将使内部循环(四个指令和两个内存访问)展开和/或转换为ldmstm。整个STM32代码集似乎越用越简单。上面的代码替代方案将根据GNU ld手册使用编译器生成,但使用uint32_t指针而不是'char'作为数据对齐。即,循环如下:

extern uint32_t _etext, _data, _edata;
uint32_t *src = &_etext;
uint32_t  *dst = &_data;
/* ROM has data at end of text; copy it.  */
while (dst < &_edata)
  *dst++ = *src++;

总的话题将是ARM memcpy()优化。由于我们控制链接器脚本,可以通过链接器脚本强制执行源对齐和大小的保证,以避免头/尾对齐问题。在我的链接器脚本中,这个对齐方式是四个字节。


3

数据段将位于RAM中。由于RAM在断电时不会保留其内容,因此需要在启动时从flash中复制数据段的初始值。为此,_sidata标签处存有.data段的初始内容副本;启动代码会将其复制到实际数据段中。


所以本质上,_sidata 应该指向 .data 段的加载地址,这个地址在 ROM 中? - bartlomiej.n
2
@bartlomiej.n 不是。_sidata 是位于 ROM 中的数据段的副本。实际的 .data 段位于 RAM 中。 - fuz
1
@bartlomiej.n,是的,.data的加载地址,但不是在ROM中:数据是可变的,因此必须加载到RAM中。 - Erik Eidt
@fuz 如果.data位于RAM中,那么链接器脚本中的_sidata符号应该指向哪里呢?还是应该在应用程序自身中硬编码地址,根据其在ROM中的位置确定地址? - bartlomiej.n
1
@bartlomiej.n 我相信我已经回答了这个问题。链接器将数据段的副本放置在ROM中,然后初始化代码将其复制到RAM中,实际数据段驻留在其中。此副本的起始地址为“_sidata”。链接器如何被指示执行取决于您使用的链接器。有关详细信息,请阅读链接器脚本和供应商提供的文档。请注意,其中没有任何硬编码内容。“_sidata”地址是由链接器根据该副本在ROM中的位置计算出来的。 - fuz
2
这里的第一个问题/断言在学究式上是正确的。_sidata指向加载地址。加载地址或LMA是ROM地址。'虚拟'地址或VMA是RAM中的地址。这是因为链接器文档是使用OS原始副本(LMA)和应用程序的MMU映射地址(VMA)的概念编写的。然而,这些名称对于Flash/ROM和RAM应用程序没有意义。因此,正确的答案是不要那样想...指的是GNU ld。 - artless noise

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接