天天看點

Zephyr OS 核心篇:系統啟動 - C 準備階段

在上一節的最後,代碼已經跳轉到 _PrepC():

  1. void _PrepC(void)
  2. {
  3.         relocate_vector_table();
  4.         enable_floating_point();
  5.         _bss_zero();
  6.         _data_copy();
  7.         _Cstart();
  8.         CODE_UNREACHABLE;
  9. }

複制代碼

咋一看,這都是 C 代碼了,那麼我們可以任意使用 C 代碼了嗎?答案是 NO!這隻是表象,此時的 C 代碼還受到限制,例如不能使用全局變量、靜态變量等。是以,這部分的 C 代碼隻是為了能夠真正暢快地運作 C 代碼做準備。

兩種啟動模式

在 Zephyr OS 中,系統有兩種啟動模式,在不同的啟動模式下需要做的準備工作不一樣。這兩種啟動模式是:

  • XIP 模式
  • 非 XIP 模式

XIP 即 eXecute In Place,中文名為“就地執行”。在 XIP 模式下,CPU 可以像直接讀記憶體一樣地直接從 (Nor) Flash 上面讀取代碼;在非 XIP 模式下,必須由硬體先将代碼從 Flash 上面搬移到 RAM 上面後,CPU 才能直接通路這些資料。

是否支援 XIP 模式與 SoC 的設計有關。

關于 XIP 的更多資訊請參考 eXecute In Place 。

重定位向量表

函數 relocate_vector_table() 用于重定位向量表:

  1. #ifdef CONFIG_XIP
  2. static inline void relocate_vector_table(void) { }
  3. #else
  4. static inline void relocate_vector_table(void)
  5. {
  6.    _scs_relocate_vector_table((void *)CONFIG_SRAM_BASE_ADDRESS);
  7. }
  8. #endif</pre>

複制代碼

對于 Cortex-M3,向量表預設位于位址 0x0 處。

對于 XIP 模式,SoC 通常會将 Flash 映射到位址 0x0 處,是以無需再對向量表進行重定位。

對于非 XIP 模式,代碼已經被已經搬移到 SRAM 中的,是以我們需要将向量表重定位寄存器指派為 SRAM 的起始位址即可。

如何知道非 XIP 模式下的向量表是位于 SRAM 中的?這從連結腳本中可以看到:

  1. <pre>#ifdef CONFIG_XIP
  2.   #define ROMABLE_REGION FLASH
  3.   #define RAMABLE_REGION SRAM
  4. #else
  5.   #define ROMABLE_REGION SRAM
  6.   #define RAMABLE_REGION SRAM
  7. #endif
  8. .....
  9. MEMORY
  10.     {
  11.     FLASH                 (rx) : ORIGIN = ROM_ADDR, LENGTH = ROM_SIZE
  12.     SRAM                  (wx) : ORIGIN = RAM_ADDR, LENGTH = RAM_SIZE
  13.     SYSTEM_CONTROL_SPACE  (wx) : ORIGIN = 0xE000E000,  LENGTH = 4K
  14.     SYSTEM_CONTROL_PERIPH (wx) : ORIGIN = 0x400FE000,  LENGTH = 4K
  15.     }
  16. SECTIONS
  17.     {
  18.     GROUP_START(ROMABLE_REGION)
  19.     _image_rom_start = CONFIG_FLASH_BASE_ADDRESS;
  20.     SECTION_PROLOGUE(_TEXT_SECTION_NAME,,)
  21.     {
  22.     KEEP(*(.exc_vector_table))
  23.     KEEP(*(".exc_vector_table.*"))
  24.     KEEP(*(.irq_vector_table))
  25.     KEEP(*(".irq_vector_table.*"))
  26.     ...
  27.     } GROUP_LINK_IN(ROMABLE_REGION)</pre>

複制代碼

我們可以看到,向量表會被連結到 ROMABLE_REGION 中,而非 XIP 模式下的 ROMABLE_REGION 即 SRAM。

非 XIP 模式下如果不進行重定位會怎樣?當發生異常或中斷時,CPU 會找到到一個錯誤的異常/中斷入口位址,然後就跑飛了。

疑問:我們的代碼是如何從 FLASH 上面跑到 SRAM 上面的?複位向量的入口位址是如何找到的?

使能浮點功能

這部分功能是可選的,略。

清 BSS 段

BSS 段是幹什麼的?首先,它是一段記憶體空間;其次,程式中的靜态變量和全局變量就是存放于這個段的。系統上電後,記憶體中的值是不可預料的,是以 BSS 段中的值也是不可預料的。對于 C 語言,我們都知道,全局變量和靜态變量的預設值是 0,要使這些變量在不初始化時的值為 0,我們必須将 BSS 段中的每個位址的值都設為 0。

BSS 段的初始化非常簡單:

  1. <pre>void _bss_zero(void)
  2. {
  3.     memset(&__bss_start, 0, ((uint32_t) &__bss_end - (uint32_t) &__bss_start));
  4. }</pre>

複制代碼

該函數直接用 memset 函數将 BSS 段中的記憶體設為 0。__bss_start 和 __bss_end 都是在連結腳本中定義的符号,它們分别表示 BSS 段的起始位址和結束位址。

資料拷貝

函數 _data_copy 用于資料拷貝:

  1. <pre>#ifdef CONFIG_XIP
  2. void _data_copy(void)
  3. {
  4.     memcpy(&__data_ram_start, &__data_rom_start, ((uint32_t) &__data_ram_end - (uint32_t) &__data_ram_start));
  5. }
  6. #else
  7. static inline void _data_copy(void)
  8. {
  9. }
  10. #endif</pre>

複制代碼

對于 XIP 模式,我們的代碼是位于 Flash 上面的,CPU 雖然可以直接像讀記憶體一樣地讀取 Flash 上面的代碼,但是速度卻比 RAM 滿多了。

對于非 XIP 模式,代碼已經位于 SRAM 上面了,是以無需任何操作。

總結

之後,系統就可以完全自由地使用 C 代碼了,我們總結一下系統上電後做了哪些必要的事兒:

  • 查找向量表,并跳轉到複位向量的入口處開始執行;
  • 屏蔽中斷。由于我們的環境還未初始化好,先屏蔽中斷;
  • 關閉看門狗。在初始化階段,我們可能無法按時喂狗,是以如果看門狗在開機後預設被使能了,則先關閉看門狗;
  • 初始化棧指針。在函數調用時,參數的傳遞、傳回值的傳遞都會有壓棧、出棧操作;
  • 重定位向量表;
  • 清 BSS 段;
  • 将代碼從 Flash 拷貝到記憶體(XIP 模式下);