此篇博客爲 SylixOS ARM BSP 編寫連載的第二篇,主要介紹 startup.S 文件具體實現。
startup.S 爲 BSP 啓動代碼入口,通常由 bootloader 裝載完 SylixOS 鏡像後調用,下面以 S3C2440A 處理器爲例,逐塊介紹 startup.S 代碼。
#ifndef ASSEMBLY #define ASSEMBLY 1 #endif
此段代碼告知後面引用的頭文件,此文件爲彙編程序。
#include <arch/assembler.h>
引用操作系統彙編頭文件,此頭文件根據編譯器和平臺類型,定義了很多編譯器與平臺相關的處理宏,可以統一不同平臺,不同編譯器之間對彙編語言關鍵字的差異。例如 FILE_BEGIN() 宏就定義在此文件,表示彙編語言文件起始。
#define UND_STACK_SIZE 0x00001000 #define ABT_STACK_SIZE 0x00001000 #define FIQ_STACK_SIZE 0x00001000 #define IRQ_STACK_SIZE 0x00001000 #define SVC_STACK_SIZE 0x00001000 #define SYS_STACK_SIZE 0x00001000
這段代碼定義 ARM 各個模式下預設堆棧的大小,一般不需要調整。
IMPORT_LABEL(archIntEntry) IMPORT_LABEL(archAbtEntry) IMPORT_LABEL(archPreEntry) IMPORT_LABEL(archUndEntry) IMPORT_LABEL(archSwiEntry) IMPORT_LABEL(sdramInit) IMPORT_LABEL(targetInit) IMPORT_LABEL(bspInit)
IMPORT_LABEL()表示此文件需要引用的外部符號,相當於 C 程序中 extern 關鍵字。
SECTION(.vector)
表示之下的所有代碼或者文字池編譯後存放在名爲 .vertor 的節區中,鏈接器根據鏈接腳本,將此節設定爲系統入口。
FUNC_DEF(vector) LDR PC, resetEntry LDR PC, undefineEntry LDR PC, swiEntry LDR PC, prefetchEntry LDR PC, abortEntry LDR PC, reserveEntry LDR PC, irqEntry LDR PC, fiqEntry FUNC_END() FUNC_LABEL(resetEntry) .word reset FUNC_LABEL(undefineEntry) .word archUndEntry FUNC_LABEL(swiEntry) .word archSwiEntry FUNC_LABEL(prefetchEntry) .word archPreEntry FUNC_LABEL(abortEntry) .word archAbtEntry FUNC_LABEL(reserveEntry) .word 0 FUNC_LABEL(irqEntry) .word archIntEntry FUNC_LABEL(fiqEntry) .word 0
FUNC_DEF()表示定義一個函數,這裏定義一個名爲 vector 的函數,根據順序,此函數將是 .vector 節區的入口函數。
FUNC_END()表示函數的結束。
這段代碼是根據 ARM 向量規範編寫的跳轉表,進入不同的異常跳轉到不同的地址,其中 ARM FIQ 快速中斷這裏並沒有處理,說明 SylixOS 默認情況下並不接管 FIQ 異常,用戶如果有需要可自行編寫相關處理函數。
以上代碼我們可以看出,系統復位向量最終跳轉到 reset 函數(或者標號)處,接下來我們看看 reset 函數實現。
SECTION(.text)
與 SECTION(.vector)含義相同,表明以下代碼或者文字池存放在 .text 節區中。這裏需要說明的是一般一個程序至少分爲三個節區(段)分別是代碼段(.text)、數據段(.data)、清零段(.bss)。其中代碼段裏面存放的一般爲程序代碼和運行時不修改的表結構;數據段中主要存放有初值的全局變量;清零段存放着在運行主要程序之前需要清零操作的全局變量。鏈接器根據鏈接腳本來確定鏈接 target 各個節區(段)的內存佈局情況。具體鏈接腳本的編寫,我們放在之後的章節介紹。
FUNC_DEF(reset) LDR R0 , =WTCON LDR R1 , =0x0 STR R1 , [R0]
reset 函數首先關閉 2440 處理器內部看門狗,以防止啓動的過程中被看門狗復位。這裏擴展說兩句,如果目標機爲 SMP (對稱多處理器)處理器,例如 ARM Cortex-A9 多核,所有的核程序入口都爲 reset,所以在 reset 函數剛開始就需要判斷此核爲主核(primary)或者從核(secondary),系統啓動時不同的核初始化順序不同,但進入多任務狀態後各個核之間不在有明確的分別。
LDR R0 , =0x31800000 /* 內核高端地址 */ MSR CPSR_c, #(UND32_MODE | DIS_INT) MOV SP , R0 SUB R0 , R0, #UND_STACK_SIZE MSR CPSR_c, #(ABT32_MODE | DIS_INT) MOV SP , R0 SUB R0 , R0, #ABT_STACK_SIZE MSR CPSR_c, #(IRQ32_MODE | DIS_INT) MOV SP , R0 SUB R0 , R0, #IRQ_STACK_SIZE MSR CPSR_c, #(FIQ32_MODE | DIS_INT) MOV SP , R0 SUB R0 , R0, #FIQ_STACK_SIZE MSR CPSR_c, #(SVC32_MODE | DIS_INT) MOV SP , R0 SUB R0 , R0, #SVC_STACK_SIZE MSR CPSR_c, #(SYS32_MODE | DIS_INT) MOV SP , R0 SUB R0 , R0, #SYS_STACK_SIZE MSR CPSR_c, #(SVC32_MODE | DIS_INT)
此段代碼初始化 ARM 處理器各種模式下的堆棧,雖然 ARM 處理器既支持堆棧從高地址向低地址增長,又支持堆棧從低地址向高地址增長,但一般情況下使用高地址向低地址增長這種模式。所以我們將堆棧其實點定義在內核高地址位置,這個地址是根據 bspMap.h 設置的內存佈局獲得的,稍後會在 bspMap.h 裏面詳細介紹。
BL sdramInit BL targetInit
初始完 ARM 堆後,我們調用兩個子函數去初始化 2440 的 SDRAM 接口與 PPL 鎖相環,當然針對不同的處理器,這裏的代碼也不盡相同,如果由 bootloader 引導,其實不需要這一部分代碼,bootloader 已經初始化好了相關的參數。
LDR R1 , =_etext /* -> ROM data end */ LDR R2 , =_data /* -> data start */ LDR R3 , =_edata /* -> end of data */ 1: CMP R2 , R3 /* check if data to move */ LDRLO R0 , [R1] , #4 /* copy it */ STRLO R0 , [R2] , #4 BLO 1b /* loop until done */
這部分代碼爲一個區域拷貝函數,我們目前只需要知道這段代碼是給數據段(.data)賦初值就可以了,因爲這裏涉及到鏈接腳本中關於裝載段與運行段不相同時的處理支持,要完全說清楚,篇幅較大,需要讀者自己根據鏈接腳本內容查閱相關知識。當然 RealCoder 會根據用戶 BSP 嚮導中用戶設置的相關參數,自動完成鏈接腳本的生成。
MOV R0 , #0 /* get a zero */ LDR R1 , =__bss_start /* -> bss start */ LDR R2 , =__bss_end /* -> bss end */ 2: CMP R1 , R2 /* check if data to clear */ STRLO R0 , [R1], #4 /* clear 4 bytes */ BLO 2b /* loop until done */
以上代碼與數據段(.data)賦初值類似,這段代碼是清零段(.bss)清零操作循環。
以上代碼可以得出一個結論:代碼段(.text)是由 bootloader 初始化的,數據段(.data)與清零段(.bss)是由 BSP 入口函數初始化的,當這三個段初始化完畢,我們就可以進入 bspInit() 函數開始正式初始化操作系統,調用 bspInit() 函數代碼如下:
LDR R10, =bspInit MOV LR , PC BX R10
startup.S 文件最後有一句 FILE_END(),它是與 FILE_BEGIN() 語句對稱使用,表明彙編程序結束。
(此篇完)