大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家分享的是IAR啟動函數(shù)流程及其__low_level_init設(shè)計對函數(shù)重定向的影響。
上一篇文章 《IAR下RT-Thread工程自定義函數(shù)段重定向失效分析》 里我們找出了影響 IAR 鏈接器處理自定義程序段重定向的原因,主要跟 __low_level_init() 函數(shù)有關(guān),這個函數(shù)屬于 IAR 底層設(shè)計,它在 IAR 啟動函數(shù) __iar_program_start() 中會被自動調(diào)用。
__iar_program_start() 是 IAR 標(biāo)準(zhǔn)啟動函數(shù),也屬于 IDE 底層設(shè)計。在任何一個 Cortex-M 廠商芯片的啟動文件里(startup_xxDevice.s)都能看到它的身影,它是復(fù)位函數(shù) Reset_Handler() 和 主函數(shù) main() 之間的橋梁,今天我們就仔細(xì)說說這個啟動函數(shù)以及其中 __low_level_init 設(shè)計:
Note 1:閱讀本文前需要對 《IAR鏈接文件(.icf)》、《IAR映射文件(.map)》 這兩種文件有所了解。Note 2:本文使用的 IAR EWARM 軟件版本是 v9.10.2。
一、通用芯片上電啟動流程
在深入挖掘 IAR 啟動函數(shù)源代碼之前,有必要先整體了解一下通用的芯片上電啟動流程,即進(jìn)入用戶 main 函數(shù)之前內(nèi)核必須要做的事情,注意這里并不包含芯片底層外設(shè)的初始化(這是因芯片而異的)。
通用啟動流程簡單來說分為如下四步:第一步是從 ROM 區(qū)域中斷向量表里獲取入口函數(shù)開始執(zhí)行,設(shè)置好初始棧指針,有了正確的棧,內(nèi)核就具備函數(shù)跳轉(zhuǎn)執(zhí)行的能力了。第二步和第三步是全局變量的初始化(將全局變量初值從 ROM 區(qū)域拷貝到變量所鏈接的 RAM 區(qū)域),全局變量初始化完成,應(yīng)用程序就有了正確的初始態(tài),最后一步就是跳轉(zhuǎn)到 main 函數(shù)。
二、從源代碼角度看啟動流程
在上一節(jié)通用啟動流程的指導(dǎo)下,我們還需要增加一些 MCU 外設(shè)相關(guān)的初始化便形成了完整的芯片啟動流程,現(xiàn)在我們從源代碼角度再來看一下具體實現(xiàn)。
2.1 典型的 Cortex-M 復(fù)位函數(shù)
我們知道復(fù)位函數(shù) Reset_Handler() 是芯片上電啟動執(zhí)行的第一個函數(shù)(有時又叫入口函數(shù)),它完成了進(jìn)入用戶 main() 函數(shù)之前的全部動作。隨便下載一家 Cortex-M 廠商芯片 SDK 包,找到 IAR 版啟動文件,其復(fù)位函數(shù)流程都差不多,這是 Cortex-M 內(nèi)核架構(gòu)決定的。
如下是典型的復(fù)位函數(shù)代碼。復(fù)位函數(shù)里的操作包括關(guān)全局中斷、設(shè)置中斷向量表首地址、設(shè)置棧頂、系統(tǒng)初始化、開全局中斷、進(jìn)啟動函數(shù)。其中系統(tǒng)初始化 SystemInit() 函數(shù)是因芯片而異的,各廠商 SDK 里會有具體源代碼實現(xiàn)(一般在 system_xxDevice.c 文件里),這里面主要做芯片硬件層面的初始化,比如關(guān)看門狗、Cache 初步設(shè)置等,保證內(nèi)核不受硬件模塊狀態(tài)影響,能正常執(zhí)行指令。
????????THUMB
????????PUBWEAK?Reset_Handler
????????SECTION?.text:CODE:REORDER:NOROOT(2)
Reset_Handler
????????CPSID???I???????????????;?Mask?interrupts
????????LDR?????R0,?=0xE000ED08
????????LDR?????R1,?=__vector_table
????????STR?????R1,?[R0]
????????LDR?????R2,?[R1]
????????MSR?????MSP,?R2
????????LDR?????R0,?=SystemInit
????????BLX?????R0
????????CPSIE???I???????????????;?Unmask?interrupts
????????LDR?????R0,?=__iar_program_start
????????BX??????R0
2.2 __iar_program_start() 到底干了啥?
上一小節(jié)里我們知道復(fù)位函數(shù)里的最后一個動作就是跳轉(zhuǎn)到啟動函數(shù),將內(nèi)核執(zhí)行權(quán)交給 __iar_program_start(),這個啟動函數(shù)源代碼并不在廠商 SDK 包里,而在 IAR 安裝目錄下,因為它是 IAR 的通用底層設(shè)計。
為了找到 __iar_program_start() 的源代碼,我們可以隨便編譯一個 SDK 例程(痞子衡選擇的是 SDK_2.11.0_MIMXRT1170-EVKboardsevkmimxrt1170demo_appshello_worldcm7iar),查看其對應(yīng)映射文件(.map),發(fā)現(xiàn)啟動函數(shù)來自于 cstartup_M.o,然后我們在 IAR 安裝目錄下搜索 cstartup_M.c/.s 文件,最終我們在如下路徑找到了啟動函數(shù)相關(guān)的全部源文件。
IAR SystemsEmbedded Workbench 9.10.2armsrclibthumbcstartup_M.s
IAR SystemsEmbedded Workbench 9.10.2armsrclibthumbcmain.s
IAR SystemsEmbedded Workbench 9.10.2armsrclibruntimelow_level_init.c
IAR SystemsEmbedded Workbench 9.10.2armsrclibinitdata_init.c
結(jié)合啟動函數(shù)相關(guān)源文件里的代碼,我們終于搞清了啟動函數(shù)全部流程,也找到了我們最關(guān)心的 __low_level_init() 函數(shù)調(diào)用位置,它在 .data/.bss/.textrw 段初始化之前被執(zhí)行,所以它的功能應(yīng)該跟 SystemInit() 差不多。默認(rèn) __low_level_init() 函數(shù)是空的,返回值是 1(返回值 0/1 決定后面的 __iar_data_init3() 要不要執(zhí)行,1 是要執(zhí)行),如果你想激活這個函數(shù),需要在自己的源文件里重新定義實現(xiàn),IAR 編譯時會優(yōu)先引用重新定義的版本。
__iar_program_start() ->
__cmain() ->
__low_level_init() -> // 底層初始化,默認(rèn)是個空函數(shù)
__iar_data_init3() -> // .data, .bss, .textrw 段初始化
main()
2.3 __low_level_init() 設(shè)計注意事項
在 EWARM_DevelopmentGuide.ENU 手冊里搜索 __low_level_init,我們可以找到這個函數(shù)的設(shè)計初衷,官方說法是為了給應(yīng)用程序一個早期初始化的機(jī)會,本質(zhì)上就是跟 SystemInit() 一樣的作用,但是因為這個 __low_level_init 函數(shù)只在 IAR 環(huán)境下適用,如果用了它,應(yīng)該程序代碼就不具備跨 IDE 的通用性,因此在各廠商 SDK 包里選擇了統(tǒng)一定義的 SystemInit() 來完成早期初始化工作。
IAR 開發(fā)手冊:IAR SystemsEmbedded Workbench 9.10.2armdocEWARM_DevelopmentGuide.ENU
EWARM_DevelopmentGuide.ENU 手冊里還特別提了幾點跟 __low_level_init 相關(guān)的注意事項,均跟 IAR 鏈接器所識別的 initialize by copy 鏈接語法有關(guān),概括來說就是因為 __low_level_init 是在 .data/.bss/.textrw 段初始化之前被執(zhí)行的,所以其代碼本身及其調(diào)用的全部代碼都不受 initialize by copy 作用,也就是這些代碼都不應(yīng)是 RAMFUNC 型。
Note: 更準(zhǔn)確地說 initialize by copy 作用范圍其實是 __iar_data_init3() 之后的代碼
三、一個 __low_level_init() 相關(guān)的重定向?qū)嶒?/h2>
最后我們再做個 __low_level_init() 相關(guān)的小實驗,在 SDK_2.11.0_MIMXRT1170-EVKboardsevkmimxrt1170demo_appshello_worldcm7iar 例程基礎(chǔ)上(flexspi_nor_debug build),創(chuàng)建一個包含如下內(nèi)容的 ramfunc_test.c 源文件,并將其添加進(jìn)工程編譯。
ramfunc_test1/3() 函數(shù)放入自定義程序段 CodeQuickAccess,ramfunc_test2/4() 函數(shù)放到默認(rèn) .textrw 段,然后重寫 __low_level_init() 函數(shù),在 __low_level_init() 函數(shù)里分別調(diào)用 ramfunc_test1/2/3/4(),其中 ramfunc_test1/2() 函數(shù)的調(diào)用在 __iar_data_init3() 前面,ramfunc_test1/2() 函數(shù)的調(diào)用在 __iar_data_init3() 后面。
void?ramfunc_test1(void)?@"CodeQuickAccess"
{
????__NOP();
}
__ramfunc?void?ramfunc_test2(void)
{
????__NOP();
}
void?ramfunc_test3(void)?@"CodeQuickAccess"
{
????__NOP();
}
__ramfunc?void?ramfunc_test4(void)
{
????__NOP();
}
//?重定義此函數(shù),讓?IAR?編譯器使用這個版本,而不是默認(rèn)版本
int?__low_level_init(void)
{
????extern?void?__iar_data_init3(void);
????ramfunc_test1();
????ramfunc_test2();
????
????//?這里增加?.data/.bss/.textrw?的初始化調(diào)用,
????//??便于區(qū)分?ramfunc_test1/2?和?ramfunc_test3/4?位置
????__iar_data_init3();
????
????ramfunc_test3();
????ramfunc_test4();
????
????return?0;
}
編譯鏈接修改后的測試工程,查看其映射文件,以及在板子上實測,得到如下結(jié)果:
結(jié)論1:放入自定義程序段的函數(shù),無論其調(diào)用位置在 __iar_data_init3() 之前還是之后,一律被 initialize by copy 忽略,函數(shù)直接鏈接在目標(biāo) RAM 區(qū),函數(shù)重定向無效;結(jié)論2:放入默認(rèn) .textrw 段的函數(shù),如果其調(diào)用位置在 __iar_data_init3() 之后,能夠被 initialize by copy 作用,函數(shù)重定向生效;結(jié)論3:放入默認(rèn) .textrw 段的函數(shù),如果其調(diào)用位置在 __iar_data_init3() 之前,從映射文件里看其能夠被 initialize by copy 作用,但在板子上實測,發(fā)現(xiàn)執(zhí)行到該函數(shù)時返回會產(chǎn)生總線錯誤,因此函數(shù)重定向也是無效的;
*******************************************************************************
*** PLACEMENT SUMMARY
***
"P1": place in [from 0x3000'2000 to 0x30fb'ffff] { ro };
"P2": place in [from 0x2000'0000 to 0x2003'fbff] { rw };
"P8": place in [from 0x0 to 0x3'ffff] { section CodeQuickAccess };
initialize by copy { rw, section .textrw, section CodeQuickAccess };
Section Kind Address Size Object
------- ---- ------- ---- ------
"P8": 0x8
CodeQuickAccess ro code 0x0 0x8 ramfunc_test.o [6]
"P2-P3|P5|P9", part 1 of 2: 0xc
RW 0x2000'0000 0xc <Block>
.textrw inited 0x2000'0004 0x8 ramfunc_test.o [6]
"P1": 0x443a
.text ro code 0x3000'63d0 0x1a ramfunc_test.o [6]
*******************************************************************************
*** MODULE SUMMARY
***
Module ro code rw code ro data rw data
------ ------- ------- ------- -------
ramfunc_test.o 34 8 8
*******************************************************************************
*** ENTRY LIST
***
Entry Address Size Type Object
---- ------- ---- ---- ------
ramfunc_test1 0x1 0x4 Code Gb ramfunc_test.o [6]
ramfunc_test2 0x2000'0005 0x4 Code Gb ramfunc_test.o [6]
ramfunc_test3 0x5 0x4 Code Gb ramfunc_test.o [6]
ramfunc_test4 0x2000'0009 0x4 Code Gb ramfunc_test.o [6]
至此,IAR啟動函數(shù)流程及其__low_level_init設(shè)計對函數(shù)重定向的影響痞子衡便介紹完畢了,掌聲在哪里~~~