天天看點

Zephyr OS 核心篇:系統啟動 - 彙編階段

不同架構的 CPU 在啟動時會略有差别,我們統一以 cortex-m3 為例。

茫茫數萬行代碼,從哪兒入手?通常,CPU 在上電後會發生一個複位異常,此時硬體内部會自動查找異常向量表,然後在該表中查找到複位向量,然後跳轉到對應的函數中開始執行。

如果時間充裕,建議閱讀閱讀《Cortex-M3 權威指南》一書,它裡面對這一塊講解得比較清楚。

向量表

cortex-m3 的異常/中斷向量表如下圖所示:

它包括兩部分,1-15 為異常向量表,16-255 為中斷向量表。

異常向量表

cortex-m3 的異常向量表所在的源檔案是 arch/arm/core/cortex-m/vector_table.s,整個檔案的源碼如下(忽略部分不相關的東西):

  1. <pre>GDATA(_main_stack)
  2. SECTION_SUBSEC_FUNC(exc_vector_table,_vector_table_section,_vector_table)
  3.     .word _main_stack + CONFIG_MAIN_STACK_SIZE
  4.     .word __reset
  5.     .word __nmi
  6.     .word __hard_fault
  7. #if defined(CONFIG_CPU_CORTEX_M0_M0PLUS)
  8.     ...
  9. #else
  10.     .word __mpu_fault
  11.     .word __bus_fault
  12.     .word __usage_fault
  13.     .word __reserved
  14.     .word __reserved
  15.     .word __reserved
  16.     .word __reserved
  17.     .word __svc
  18.     .word __debug_monitor
  19. #endif
  20.     .word __reserved
  21.     .word __pendsv
  22. #if defined(CONFIG_CORTEX_M_SYSTICK)
  23.     .word _timer_int_handler
  24. #else
  25.     ...
  26. #endif</pre>

複制代碼

GDATA(_main_stack) 的作用是做一個申明,類似于 C 語句中的 extern 關鍵字,我們不必深究,隻需要知道它表示 _main_stack 是一個全局的符号,且存放于資料段。

SECTION_SUBSEC_FUNC(exc_vector_table,_vector_table_section,_vector_table) 表示 _vector_table 是一個符号,且該符号屬于 exc_vector_table 這個段中的 _vector_table_section 子段。我們可以簡單地将 SECTION_SUBSEC_FUNC() 了解為實作了一個函數。

符号是什麼?它表示一個位址。比如我們寫了一個 C 函數 test(),則編譯器在彙編階段會根據這個函數名生成一個符号 test(假設叫做 test,實際的符号名可能是xxx_xxx_test),而這個符号的後面會緊跟着該函數的代碼,當其它代碼需要調用該函數時,會使用跳轉指令 bx test,然後就會執行該函數的代碼了。是以,符号的本質是一個位址,而該位址處存放着可以執行的代碼(指令)。

後面的形式都是固定,都是 .word xxxx。其中,.word 是一個彙編僞指令,表示後面的符号的類型是 word;xxxx 表示一個符号(第一個 _main_stack + CONFIG_MAIN_STACK_SIZE 除外),當發生對應的異常時,都會跳轉到該符号(即位址)對應的代碼處執行。

當 CPU 上電後會發生複位異常,即會跳轉到 __reset 符号處繼續執行,是以我們會繼續分析 __reset 處的代碼。

中斷向量表

cortex-m3 的中斷向量表所在的源檔案是 arch/arm/core/cortex-m/irq_vector_table.s。系統啟動時不會涉及到這一塊,略。

__reset

__reset 這“函數”的實作在 arch/arm/core/cortex-m/reset.S 中:

  1. GTEXT(__reset)
  2. <pre>GTEXT(memset)
  3. GDATA(_interrupt_stack)
  4. SECTION_SUBSEC_FUNC(TEXT,_reset_section,__reset)
  5. #if defined(CONFIG_CPU_CORTEX_M0_M0PLUS)
  6.     ...
  7. #else
  8.     movs.n r0, #_EXC_IRQ_DEFAULT_PRIO
  9.     msr BASEPRI, r0
  10. #endif
  11. #ifdef CONFIG_WDOG_INIT
  12.     bl _WdogInit
  13. #endif
  14. #ifdef CONFIG_INIT_STACKS
  15.     ldr r0, =_interrupt_stack
  16.     ldr r1, =0xaa
  17.     ldr r2, =CONFIG_ISR_STACK_SIZE
  18.     bl memset
  19. #endif
  20.     ldr r0, =_interrupt_stack
  21.     ldr r1, =CONFIG_ISR_STACK_SIZE
  22.     adds r0, r0, r1
  23.     msr PSP, r0
  24.     movs.n r0, #2     
  25.     msr CONTROL, r0
  26.     b _PrepC</pre>

複制代碼 GDATA()、GTEXT() 都是對符号的申明,不再贅述。

SECTION_SUBSEC_FUNC(TEXT,_reset_section,__reset) 表示後面的代碼屬于 __reset() “函數”。

總結

CPU 在上電後做了如下幾件事兒:

  • 查找向量表,并跳轉到複位向量的入口處繼續執行
  • 鎖定中斷
  • 初始化看門狗(如果有需要)
  • 初始化中斷棧空間(如果有需要)
  • 将棧指針 PSP 指向中斷棧空間的頂部
  • 跳轉換到 _PrepC,繼續為運作 C 環境做準備