大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是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主頁、知乎主頁、微信公眾號 平台上。
微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。