此篇博客为 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() 语句对称使用,表明汇编程序结束。
(此篇完)