在上一節的最後,代碼已經跳轉到 _PrepC():
- void _PrepC(void)
- {
- relocate_vector_table();
- enable_floating_point();
- _bss_zero();
- _data_copy();
- _Cstart();
- CODE_UNREACHABLE;
- }
複制代碼
咋一看,這都是 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() 用于重定位向量表:
- #ifdef CONFIG_XIP
- static inline void relocate_vector_table(void) { }
- #else
- static inline void relocate_vector_table(void)
- {
- _scs_relocate_vector_table((void *)CONFIG_SRAM_BASE_ADDRESS);
- }
- #endif</pre>
複制代碼
對于 Cortex-M3,向量表預設位于位址 0x0 處。
對于 XIP 模式,SoC 通常會将 Flash 映射到位址 0x0 處,是以無需再對向量表進行重定位。
對于非 XIP 模式,代碼已經被已經搬移到 SRAM 中的,是以我們需要将向量表重定位寄存器指派為 SRAM 的起始位址即可。
如何知道非 XIP 模式下的向量表是位于 SRAM 中的?這從連結腳本中可以看到:
- <pre>#ifdef CONFIG_XIP
- #define ROMABLE_REGION FLASH
- #define RAMABLE_REGION SRAM
- #else
- #define ROMABLE_REGION SRAM
- #define RAMABLE_REGION SRAM
- #endif
- .....
- MEMORY
- {
- FLASH (rx) : ORIGIN = ROM_ADDR, LENGTH = ROM_SIZE
- SRAM (wx) : ORIGIN = RAM_ADDR, LENGTH = RAM_SIZE
- SYSTEM_CONTROL_SPACE (wx) : ORIGIN = 0xE000E000, LENGTH = 4K
- SYSTEM_CONTROL_PERIPH (wx) : ORIGIN = 0x400FE000, LENGTH = 4K
- }
- SECTIONS
- {
- GROUP_START(ROMABLE_REGION)
- _image_rom_start = CONFIG_FLASH_BASE_ADDRESS;
- SECTION_PROLOGUE(_TEXT_SECTION_NAME,,)
- {
- KEEP(*(.exc_vector_table))
- KEEP(*(".exc_vector_table.*"))
- KEEP(*(.irq_vector_table))
- KEEP(*(".irq_vector_table.*"))
- ...
- } 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 段的初始化非常簡單:
- <pre>void _bss_zero(void)
- {
- memset(&__bss_start, 0, ((uint32_t) &__bss_end - (uint32_t) &__bss_start));
- }</pre>
複制代碼
該函數直接用 memset 函數将 BSS 段中的記憶體設為 0。__bss_start 和 __bss_end 都是在連結腳本中定義的符号,它們分别表示 BSS 段的起始位址和結束位址。
資料拷貝
函數 _data_copy 用于資料拷貝:
- <pre>#ifdef CONFIG_XIP
- void _data_copy(void)
- {
- memcpy(&__data_ram_start, &__data_rom_start, ((uint32_t) &__data_ram_end - (uint32_t) &__data_ram_start));
- }
- #else
- static inline void _data_copy(void)
- {
- }
- #endif</pre>
複制代碼
對于 XIP 模式,我們的代碼是位于 Flash 上面的,CPU 雖然可以直接像讀記憶體一樣地讀取 Flash 上面的代碼,但是速度卻比 RAM 滿多了。
對于非 XIP 模式,代碼已經位于 SRAM 上面了,是以無需任何操作。
總結
之後,系統就可以完全自由地使用 C 代碼了,我們總結一下系統上電後做了哪些必要的事兒:
- 查找向量表,并跳轉到複位向量的入口處開始執行;
- 屏蔽中斷。由于我們的環境還未初始化好,先屏蔽中斷;
- 關閉看門狗。在初始化階段,我們可能無法按時喂狗,是以如果看門狗在開機後預設被使能了,則先關閉看門狗;
- 初始化棧指針。在函數調用時,參數的傳遞、傳回值的傳遞都會有壓棧、出棧操作;
- 重定位向量表;
- 清 BSS 段;
- 将代碼從 Flash 拷貝到記憶體(XIP 模式下);