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

痞子衡 發表於 2021-11-27
嵌入式

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是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啟動函式流程之段初始化函式__iar_data_init3實現

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

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

歡迎訂閱

文章會同時釋出到我的 部落格園主頁CSDN主頁知乎主頁微信公眾號 平臺上。

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

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