痞子衡嵌入式:深扒IAR啟動函數流程之段初始化函數__iar_data_init3實現



  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是IAR啟動函數流程里的段初始化函數__iar_data_init3實現

  本篇是 《IAR啟動函數流程及其__low_level_init設計對函數重定向的影響》 一文的后續,在上篇文章里我們在 IAR 軟件安裝目錄下找到了標准啟動函數 __iar_program_start() 相關源文件,並且分析了 __iar_program_start() 函數里的全部動作。我們知道了其中負責 .data/.bss/.textrw 段初始化工作的是 __iar_data_init3() 函數,但是這個函數的具體實現並沒有詳細介紹,今天我們就仔細說說這個 __iar_data_init3() 函數:

  • Note 1: 閱讀本文前需要對 《IAR鏈接文件(.icf)》 有所了解。
  • Note 2: 本文使用的 IAR EWARM 軟件版本是 v9.10.2。

一、為什么有些段需要初始化?

  《IAR鏈接文件(.icf)》 一文第一小節列出了 IAR 工程里定義的全部系統段(Section)名,其中 .data/.bss/.textrw 段是需要初始化的,因為這些段是鏈接在 RAM 里,而 RAM 上電其內容都是隨機值,所以需要一段啟動代碼將 .data/.bss/.textrw 段所在的 RAM 區填上對應的初值(初值來自於下載了程序鏡像文件的 Flash 區),然后應用程序才能正常運行。

  • Note: 除了 .data/.bss/.textrw 之外,還有一些段(.noinit/CSTACK/HEAP等)也鏈接在 RAM 區,但這些段對初值沒有依賴,所以不需要初始化。
.bss                 // 初值為 0 的靜態/全局變量(RAM)
.data                // 初值為非 0 的全局變量(RAM)
.data_init           // .data 段的初值(Flash)
.textrw              // __ramfunc 修飾的重定向函數實際執行區(RAM)
.textrw_init         // .textrw 段的機器碼存儲區(Flash)

二、RW/ZI段初始化的一般實現

  應用程序工程在編譯鏈接結束后,.data/.bss/.textrw 段實際鏈接地址就確定了(這里指默認由 IAR 鏈接器自由分配具體鏈接地址,而不是用戶在鏈接文件中指明具體鏈接地址的情況),我們知道了這些段的鏈接地址,就可以完成對應初始化工作(說白了,就是初值數據從 Flash 到 RAM 的拷貝工作),實際鏈接地址可以通過如下 IAR 鏈接器提供的接口來獲取,具體拷貝過程可參看 《IAR下將關鍵函數重定向到RAM中執行的方法》 一文最后一節里的代碼。

  • Note: IAR 鏈接器為了后續初始化的方便,都是將程序中全部的全局變量緊挨着放到一塊連續的 RAM 區域(.data),然后其全部初值也一一對應緊挨着放一起(.data_init,下載到一塊連續的 Flash 區);對於 .textrw 的處理也類似。
#pragma section = ".data"
#pragma section = ".data_init"
#pragma section = ".bss"
#pragma section = ".textrw"
#pragma section = ".textrw_init"

uint8_t *data_ram              = __section_begin(".data");
uint8_t *data_rom              = __section_begin(".data_init");
uint8_t *data_rom_end          = __section_end(".data_init");
uint8_t *bss_start             = __section_begin(".bss");
uint8_t *bss_end               = __section_end(".bss");
uint8_t *code_relocate_ram     = __section_begin(".textrw");
uint8_t *code_relocate_rom     = __section_begin(".textrw_init");
uint8_t *code_relocate_rom_end = __section_end(".textrw_init");

  段初始化的一般實現雖然簡單,但有些缺點,就是對於用戶自定義 RW/ZI 段或者多個分散的 RW/ZI 段無法自動適應,需要根據實際情況不斷調整代碼實現。而且也不能用於 IAR 鏈接器有對 .data_init/.textrw_init 段做了壓縮的情況(不過鏈接文件里使用 initialize manually 不帶額外參數的話,默認是關了壓縮功能)。

三、__iar_data_init3() 函數實現細節

  前面鋪墊了這么多,終於到了圍觀 IAR 標准段初始化函數 __iar_data_init3() 實現的時候了,跟這個函數相關的源文件在如下路徑下,核心代碼在 data_init.c 文件中:

\IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\init\data_init.c
\IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\init\zero_init3.c  - 存放 __iar_zero_init3 函數
\IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\init\copy_init3.c  - 存放 __iar_copy_init3 函數

  在 data_init.c 文件中有一個叫 IAR_DATA_INIT 的函數,其實它就是 __iar_data_init3,光看這個函數里的代碼會讓人有點摸不着頭腦,因為用了 IAR 鏈接器里的接口及一些特殊定義,我們結合一個具體應用程序工程來講解會更清晰。

// 在 IAR 目錄 \arm\inc\c\DLib_Product.h 中宏定義
#define _DLIB_ELF_INIT_INTERFACE_VERSION 3

// 在 IAR 目錄 \arm\src\lib\init\data_init.h 中的宏定義
#define IAR_DATA_INIT _GLUE(__iar_data_init, _DLIB_ELF_INIT_INTERFACE_VERSION)

#pragma section = "Region$$Table" const TABLE_MEM
void IAR_DATA_INIT(void)
{
    FAddr TABLE_MEM const * pi = __section_begin("Region$$Table");
    table_ptr_t             pe = __section_end  ("Region$$Table");
    while (pi != pe)
    {
        init_fun_t * fun = FAddr_GetPtr(pi);
        ++pi;
        pi = fun(pi);
    }
}

  我們現在隨便編譯一個 SDK 例程(痞子衡選擇的是 \SDK_2.11.0_MIMXRT1170-EVK\boards\evkmimxrt1170\demo_apps\hello_world\cm7\iar,切到 flexspi_nor_debug build,即代碼 RO 段鏈接在 0x30000000 開始的 Flash 區,代碼 RW 段鏈接在 0x20000000 開始的 DTCM 區),查看其對應映射文件(.map),摘出其中跟段初始化相關的一些內容如下,初始化工作包含:利用 __iar_zero_init3 函數清零起始地址為 0x20000040 長度為 0x4c 字節的 ZI 段空間,利用 __iar_copy_init3 函數拷貝 0x40 字節 RW 段數據(從 0x300060fc 到 0x20000000):

  • Note: 映射文件里 INIT TABLE 區,Copy 動作下,source range 與 destination range 一樣大,說明測試 SDK 例程下 IAR 鏈接器沒有對 .data_init/.textrw_init 段進行壓縮。
*******************************************************************************
*** INIT TABLE
***

          Address      Size
          -------      ----
Zero (__iar_zero_init3)
    1 destination range, total size 0x4c:
          0x2000'0040  0x4c

Copy (__iar_copy_init3)
    1 source range, total size 0x40:
          0x3000'60fc  0x40
    1 destination range, total size 0x40:
          0x2000'0000  0x40

*******************************************************************************
*** ENTRY LIST
***

Entry                       Address   Size  Type      Object
 ----                       -------   ----  ----      ------
.iar.init_table$$Base   0x3000'63d4          --   Gb  - Linker created -
.iar.init_table$$Limit  0x3000'63f8          --   Gb  - Linker created -
Region$$Table$$Base     0x3000'63d4          --   Gb  - Linker created -
Region$$Table$$Limit    0x3000'63f8          --   Gb  - Linker created -
__iar_copy_init3        0x3000'630d   0x2c  Code  Gb  copy_init3.o [6]
__iar_zero_init3        0x3000'613d   0x3c  Code  Gb  zero_init3.o [6]

  在映射文件里,我們知道了 Region$$Table 區域的起止地址 [0x300063d4 - 0x300063f8),打開鏡像文件或者在線調試找到這段區域里的內容,你會發現段初始化工作所需的全部信息(操作函數地址、操作數據長度、操作源地址、操作目標地址)都記錄在里面,其中特別注意的是涉及 Flash 區的地址都是以相對地址來存放的(FAddr_GetPtr 函數負責地址轉換):

  • Note1:0x300063d4 地址處的值是 0xfffffd69,那么 0x300063d4 + 0xfffffd69 = 0x13000613d,保留低 32bit 即是 __iar_zero_init3 函數地址。
  • Note2:0x300063e4 地址處的值是 0xffffff29,那么 0x300063e4 + 0xffffff29 = 0x13000630d,保留低 32bit 即是 __iar_copy_init3 函數地址。

  現在我們就很好理解 __iar_data_init3 函數里的代碼了,它就是從 Region$$Table 區域里按序取出初始化工作所需的信息,並去一一執行完成段初始化的工作,這種實現方法的優點在於拓展性強,IAR 鏈接器可根據實際應用程序工程的鏈接情況自由拓展 Region$$Table 區域里的內容,而 __iar_data_init3 函數本身則不需要做任何修改。

  至此,IAR啟動函數流程里的段初始化函數__iar_data_init3實現痞子衡便介紹完畢了,掌聲在哪里~~~

歡迎訂閱

文章會同時發布到我的 博客園主頁CSDN主頁知乎主頁微信公眾號 平台上。

微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM