• 正文
    • 一、通用芯片上電啟動流程
    • 二、從源代碼角度看啟動流程
    • 三、一個 __low_level_init() 相關(guān)的重定向?qū)嶒?/span>
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

深扒IAR啟動函數(shù)流程及其底層初始化設(shè)計

03/26 14:10
410
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

大家好,我是痞子衡,是正經(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ù)重定向的影響痞子衡便介紹完畢了,掌聲在哪里~~~

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

碩士畢業(yè)于蘇州大學(xué)電子信息學(xué)院,目前就職于恩智浦(NXP)半導(dǎo)體MCU系統(tǒng)部門,擔(dān)任嵌入式系統(tǒng)應(yīng)用工程師。痞子衡會定期分享嵌入式相關(guān)文章