【嵌入式开发学习】__搞了多年嵌入式,才发现全局变量是这样初始化的

最近,有个好学的小伙子突然问了我一个问题:

全局变量的初始值,是在哪里赋值的?

这个问题虽然说不是很重要,但是我很好奇。

为了给讲清楚这个原理过程,我专门建立一个基于 Renesas RH850 的简单工程,挖一挖里面的技术细节。

我在 main.c 中定义了随便这几个变量

int counter, accumulator = 0, limit_value = 1000000;
unsigned char str_aa55[2] = {0xAA,0x55};
unsigned int int_1122334455667788 = 0x11223344;
unsigned int int_55667788 = 0x55667788;
int bss_val;

void main(void)
{
    
}

然后,直接仿真查看,跟你我想的一样,在 main函数 之前就初始化完成了,即这些变量都自动初始化赋值了。

让人好奇的是,它是怎么做到的?

单片机的启动程序一般都是很简单的,即使汇编也没多少行,直接翻出来看看也许会知道答案。

-- Clear local RAM
  mov ___ghs_ramstart, r6  -- start of local RAM
  mov ___ghs_ramend, r7    -- end of local RAM
  mov r0, r1
1:
  st.dw   r0, 0[r6]
  addi    8, r6, r6
  cmp r7, r6
  bl 1b
  -- Jump to the HW initialisation function
  jarl  ___lowinit, lp
  -- Jump to the initialisation functions of the library
  -- and from there to main()
  jr __start

以上这段汇编,根据旁边的注释其实很容易理解,前半部分就是将内存Local RAM初始化清零,即这段汇编可以见到梳理成

RAM清零 ——> 执行 ___lowinit ——> 执行 __start ——> 进入 main函数

既然前面给RAM清零了,那么此时的全局变量应该全是0值吧,那可以推测,给全局变量赋初始值应该是在 ___lowinit__start 了,但是这两个东西是编译环境里某个库的,暂时看不到源码。

但是,最终通过仿真查看变量值的方式,可以定位,给全局变量赋初始值是在 __start 里面。

此时,虽然我知道了它在哪里给全局变量初始化了,但是并不知道是怎样初始化的。

我还是很好奇,本着刨根问底的精神继续挖掘。

但是我在这个 RH850的代码工程 里面是找不到这个 __start 的源码内容的,仿真看汇编折腾了半天,突然想了下,为啥不换个其他工程试试,例如试试 NXP S32K 的?

于是,我创建了一个 NXP S32K1xx 的代码工程,仍然定义这几个变量

int counter, accumulator = 0, limit_value = 1000000;
unsigned char str_aa55[2] = {0xAA,0x55};
unsigned int  int_1122334455667788 = 0x11223344;
unsigned int  int_55667788 = 0x55667788;
int bss_val;

 同样的讨论,直接翻启动文件的汇编代码

/* Init .data and .bss sections */
    ldr     r0,=init_data_bss
    blx     r0

还是欧美的芯片简单粗暴,不像小日子做的初始化还有藏进库里。很明显,init_data_bss 就是初始化全局变量的,以下截取了部分代码,也很容易理解。 


void init_data_bss(void)
{
    /* ...... */
    /* Data */
    data_ram        = (uint8_t *)__DATA_RAM;
    data_rom        = (uint8_t *)__DATA_ROM;
    data_rom_end    = (uint8_t *)__DATA_END;
    /* ...... */
    /* BSS */
    bss_start       = (uint8_t *)__BSS_START;
    bss_end         = (uint8_t *)__BSS_END;
    /* ...... */
    
    /* Copy initialized data from ROM to RAM */
    while (data_rom_end != data_rom)
    {
        *data_ram = *data_rom;
        data_ram++;
        data_rom++;
    }
    
    /* ...... */
    /* Clear the zero-initialized data section */
    while(bss_end != bss_start)
    {
        *bss_start = 0;
        bss_start++;
    }
    /* ...... */
}

data段 data_ram 的初始化内容就是从 data_rom 来,而 data_rom 是从 __DATA_ROM 来。

那么,__DATA_ROM 是什么东西,从哪里呢?

搜一搜工程里面的代码,很简单,这是从ld文件来

/* Specify the memory areas */
MEMORY
{
  /* … */
  /* SRAM_L */
  m_data   (RW)  : ORIGIN = 0x1FFF8000, LENGTH = 0x00008000
 m_data_2  (RW)  : ORIGIN = 0x20000000, LENGTH = 0x00007000

  /* … */
  .data : AT(__DATA_ROM)
  {
    . = ALIGN(4);
    __DATA_RAM = .;
    __data_start__ = .;      /* Create a global symbol at data start. */
    *(.data)                 /* .data sections */
    *(.data*)                /* .data* sections */
    . = ALIGN(4);
    __data_end__ = .;        /* Define a global symbol at data end. */
  } > m_data

  __DATA_END = __DATA_ROM + (__data_end__ - __data_start__);
  __CODE_ROM = __DATA_END; /* Symbol is used by code initialization. */


  /* Uninitialized data section. */
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss section. */
    . = ALIGN(4);
    __BSS_START = .;
    __bss_start__ = .;
    *(.bss)
    *(.bss*)
    *(COMMON)
    . = ALIGN(4);
    __bss_end__ = .;
    __BSS_END = .;
  } > m_data_2

这里简单介绍下,带有初始化值(非0)全局变量(例如unsigned int  int_55667788 = 0x55667788;),都是定义在 data段 的,而未定义初始化值的全局变量,是分在 bss段 的(例如int bss_val;)。

到底是不是我说的这样子,直接查看map文件中的变量名和对应地址或段名就知道了

.data           0x1fff8400      0x42c load address 0x000009cc
                0x1fff8400                . = ALIGN (0x4)
                0x1fff8400                __DATA_RAM = .
                0x1fff8400                __data_start__ = .
 *(.data)
 *(.data*)
 .data.limit_value
                0x1fff8400        0x4 ./src/main.o
                0x1fff8400                limit_value
 .data.str_aa55
                0x1fff8404        0x2 ./src/main.o
                0x1fff8404                str_aa55
 *fill*         0x1fff8406        0x2 
 .data.int_11223344
                0x1fff8408        0x4 ./src/main.o
                0x1fff8408                int_11223344
 .data.int_55667788
                0x1fff840c        0x4 ./src/main.o
                0x1fff840c                int_55667788


.bss            0x20000000       0x28
                0x20000000                . = ALIGN (0x4)
                0x20000000                __BSS_START = .
                0x20000000                __bss_start__ = .
 *(.bss)
 *(.bss*)
 .bss.accumulator
                0x2000001c        0x4 ./src/main.o
                0x2000001c                accumulator
 *(COMMON)
 COMMON         0x20000020        0x8 ./src/main.o
                0x20000020                bss_val
                0x20000024                counter
                0x20000028                . = ALIGN (0x4)
                0x20000028                __bss_end__ = .
                0x20000028                __BSS_END = .

                0x000009cc                __DATA_ROM = .

另外,从这map文件里也可以看到,这个__DATA_ROM对应的地址是0x000009cc,也就是说,这些 

int limit_value = 1000000;
unsigned char str_aa55[2] = {0xAA,0x55};
unsigned int  int_1122334455667788 = 0x11223344;
unsigned int  int_55667788 = 0x55667788;

等等变量的的初始值是来源于 0x000009cc 这里。

那就直接查看生成的 hex文件

【嵌入式开发学习】__搞了多年嵌入式,才发现全局变量是这样初始化的_第1张图片

是不是很巧,是不是很妙?!

不过,还是很好奇,这是怎么做到的,怎么恰巧这些值就在这个地址呢?

秘密就在于ld文件里的这个语句:.data : AT(__DATA_ROM)

意思是,定义在data段的变量对应的初始化值,就放在 __DATA_ROM 中。就这么简单,剩下的交给编译器就行了。

这里顺便提一下,排查跟踪这些信息需要掌握编译链接相关知识,特别是链接脚本和生成的map内容,我之前也跟我身边的很多小伙伴讲过这些内容,但是我建议系统地看看这些内容。我之前是无意中从《程序员的自我修养》这本书学到的,最大的感受就是让我得到了启发,在往后的编程日子里更关注MCU地址、编译和链接方面的内容,对排查底层问题相当有帮助。


(* ̄︶ ̄)创作不易!期待你们的 点赞、收藏和评论喔。 

本文来源网络,免费分享知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除!

你可能感兴趣的:(嵌入式开发,学习,嵌入式硬件,嵌入式,全局变量)