MCU變數載入過程

前言

在開發mcu程式碼的時候經常會有些疑惑,變數是怎麼在編譯之後進入單片機的ram區的呢,特別是在使用keil開發的時候。後來在接觸gcc編譯器和自研的mcu後,終於明白了這個問題。實際上變數編譯後被放在了bin文件中程式碼的後面。程式運行時會主動將該區域的數據依次搬運到ram區域中。

原理

寫完程式碼編譯後,會把code中的所有變數放在程式碼的後面,當然這個規則也是由鏈接文件來決定的。示例圖如下
image
在程式運行後正式進入C環境下,code中會存在一段彙編程式碼。主要作用就是把data區域記憶體依次複製到ram中,複製結束後把後面和bss長度一致區域的記憶體全置為0。

通過這也能夠看出為什麼bss區域的值會被設置為0,因為記憶體中是沒有類型的說法。如果不全置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區的起始、結束地址,bss區的起始、結束地址都是通過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: