痞子衡嵌入式:深扒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_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):

*******************************************************************************
*** 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 區的地址都是以相對地址來存放的:

  • Note:FAddr_GetPtr 函數負責地址轉換,0x300063d4 地址處的值是 0xfffffd69,那麼 0x300063d4 + 0xfffffd69 = 0x13000613d,保留低 32bit 即是 __iar_zero_init3 函數地址。

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

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

歡迎訂閱

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章