前言

在开发mcu代码的时候经常会有些疑惑,变量是怎么在编译之后进入单片机的ram区的呢,特别是在使用keil开发的时候。后来在接触gcc编译器和自研的mcu后,终于明白了这个问题。实际上变量编译后被放在了bin文件中代码的后面(data存放在bin中,bss在bin里存放了长度信息)。程序运行时会主动将该区域的数据依次加载到ram区域中。

原理

写完代码编译后,会把code中的data区的变量放在代码的后面,bss区的变量仅存放长度在bin中,当然这个规则也是由链接文件来决定的。示例图如下
MCU变量加载过程-LMLPHP
在程序运行后正式进入C环境前,code中会存在一段汇编代码。主要作用就是把data区域内存依次复制到ram中,复制结束后。把后面和bss区描述长度一致区域的内存全置为0。

但是在keil中我们没有看到这样的代码。keil的汇编代码如下:

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  SystemInit
                IMPORT  __main
                LDR     R0, =SystemInit
                BLX     R0
                LDR     R0, =__main
                BX      R0
                ENDP

这段代码执行完毕后会跳转到__main函数里执行,主要注意的是这里并不是直接跳转到我们编写的main函数里。在__main有很多的操作,其中就包括将变量加载到ram区域中。执行完毕后才会跳转到我们自己开发的main函数中执行。

示例分析

下面这段代码就是gcc编译器下加载变量的代码。其中有几个变量,data在bin文件中的起始地址,data在ram区的起始、结束地址,bss在ram区的起始、结束地址都是通过ld文件中获取的。
运行完下面的code后,程序中的所有变量都被加载到了ram区域中。

/*
 *  The ranges of copy from/to are specified by following symbols
 *    __etext: LMA of start of the section to copy from. Usually end of text
 *    __data_start__: VMA of start of the section to copy to
 *    __data_end__: VMA of end of the section to copy to
 *
 *  All addresses must be aligned to 4 bytes boundary.
 */
    lrw     r1, __erodata       // data在bin文件中的起始地址
    lrw     r2, __data_start__  // data在ram中的起始地址
    lrw     r3, __data_end__    // data在ram中的结束地址

    subu    r3, r2              // r3为data的长度
    cmpnei  r3, 0               // 判断长度是否为0
    bf      .L_loop0_done

.L_loop0:                       // 将bin文件中数据依次移到ram中
    ldw     r0, (r1, 0)
    stw     r0, (r2, 0)
    addi    r1, 4
    addi    r2, 4
    subi    r3, 4
    cmpnei  r3, 0
    bt      .L_loop0

.L_loop0_done:

/*
 *  The BSS section is specified by following symbols
 *    __bss_start__: start of the BSS section.
 *    __bss_end__: end of the BSS section.
 *
 *  Both addresses must be aligned to 4 bytes boundary.
 */
    lrw     r1, __bss_start__
    lrw     r2, __bss_end__

    movi    r0, 0

    subu    r2, r1
    cmpnei  r2, 0
    bf      .L_loop1_done

.L_loop1:                       // 将bss里的数据复制为0
    stw     r0, (r1, 0)
    addi    r1, 4
    subi    r2, 4
    cmpnei  r2, 0
    bt      .L_loop1
.L_loop1_done:
12-29 19:34