U-Boot是一个开源的系统引导,是用开引导启动内核的。它的整个工作可以分为两个部分,两部分合力完成把程序加载到内存中启动内核的工作。U-Boot是由根据Linux源代码改变删减发展而来的,所以U-Boot工作的两部分中的第一部分的源代码主要由汇编语言完成硬件的初始化,搭建C语言可以运行的环境,第二部分主要由C语言完成剩余其他工作。这样的系统的移植性会更好。
start.S文件存放在U-Boot系统的CPU目录下,而CPU目录下存放的正是和处理器相关的操作文件。start.S文件是U-Boot启动时执行的第一个文件来完成U-Boot启动的第一部分操作,来完成硬件的一系列初始化工作,为后续的操作做铺垫。
通过查资料和分析我们发现无论是什么语言C语言、汇编语言都需要一个标志,告诉系统,通过标志来找到文件可执行的入口位置。C语言规定的main( )为执行的起点。而我们这里u-boot.lds文件中是通过查找链接脚本中的ENTRY声明位置,中的关键字符号,来确定该符号所在文件,所在位置就是整个程序的起始位置。也就是说,通过ENTRY(_start)便可确定start.S文件是我们整个程序的起始代码所在文件。
具体查看下列代码:
/*
* (C) Copyright 2002
* Gary Jennejohn, DENX Software Engineering,
* See file CREDITS for list of people who contributed to this project.
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")* OUTPUT_ARCH(arm)
ENTRY(_start)
/* ↑这里这里 */
通过查看U-Boot的源代码。我们找到了start.S的部分源代码,从而可以进行进一步的分析。
1. “config.h”、”version.h”是在include目录下,他们两个都是在配置过程中会自动生产的文件,在他们的文件中右包含了其他文件,包含很多配置过程需要调用的宏,把子文件封装在该文件中,增加了系统的可移植性。
#include< config.h >
#include< version.h >
/*
*************************************************************************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************************
*/
2. “.globl ” 是个关键字,它的作用是声明全局标志,也就是说_start这个符号是可以被外部调用使用的。 _start是我们在上文提到的定义的标志起始代码的符号。
.globl _start
3. _start:标识这端代码的起点,也就是说当系统找到start时,就会从这个位置开始执行。
b 是跳转指令,意思就是跳转到reset标志处执行,然后返回时返回此处。当CPU复位后真正去执行的是reset处的代码。也就是说,_start并不是真正的代码其实,reset才是。
_start: b reset
4. 这部分是CPU处理器的异常处理向量表,设置一系列的中断向量,这些异常向量是根据硬件设计的。ARM支持7种异常中断。
当异常出现后,ARM会保存当前运行的下一条指令到寄存器LR中,将状态寄存器(CPSR)复制到备份的程序状态计数器(SPSR)中,根据这7中异常类型选择,通过设置PC程序计数器跳转执行7中异常向量所定义的异常处理程序。处理完返回。
ldr pc, _undefined_instruction
/*1.未定义指令的异常向量*/
ldr pc, _software_interrupt
/*2.软件中断的异常向量*/
ldr pc, _prefetch_abort
/*3.预取指令操作异常向量*/
ldr pc, _data_abort
/*4.数据操作异常向量*/
ldr pc, _not_used
/*5.未使用*/
ldr pc, _irq
/*6.慢速中断异常向量*/
ldr pc, _fiq
/*7.快速中断异常向量*/
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
/*利用.word来在所在地址放置一个16bits值,这个值作为常量使用,实际上就是对应的中断处理函数的地址*,通过这一操作完成跳转*/
5.这条指令的意思是地址对齐排布。为了提高访存效率,要求地址对齐。也就是说如果当前地址不对齐16的倍数时,填充0xdeadbeef直到走到满足对齐16的倍数的位置继续操作。
.balignl 16,0xdeadbeef
/*
*************************************************************************
* Startup Code (called from the ARM reset exception vector)
* do important init only if we don't start from memory!
* relocate armboot to ram
* setup stack
* jump to second stage
*************************************************************************
*/
6.这里是在保存变量的数据区对其主要是保存一些配置所需要的全局变量完成定义初始化操作,同理为了外部可以使用。这些变量主要用于连接脚本,以及将系统载入到内存中等操作。
_TEXT_BASE:
.word TEXT_BASE
/*这个变量在文件/board/coteca9/config.mk中定义*/
.globl _armboot_start
_armboot_start:
.word _start
/*用_start定义_armboot_start,未初始化数据段开始地址*/
/*
* These are defined in the board-specific linker script.
*/
.globl _bss_start
_bss_start:
.word __bss_start
/*_bss_start是链接脚本u-boot.lds中定义的,其中存储的是该标号所在地址。*/
.globl _bss_end
_bss_end:
.word _end
/*结束地址*/
7.完成系统的配置操作,以及设置中断的堆栈起始地址
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
/*判断中断是否已经配置*/
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
/*指定堆栈的起始地址*/
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif
8.这就是我们上文说的系统启动复位真正的start。也就是说,系统一启动系统实际上会通过ENTRY、b 跳转至这里真正的开始执行,系统上电启动后,默认的cpu的pc会执行0x0地址处。
设置CPSR为超级保护模式,让CPU运行在操作系统保护模式,为后续的操作来做准备,ARM系统共有用户模式、快速中断、外部中断、系统模式、超级保护模式等7种模式,而在SVC超级安全模式下,超级用户态,操作系统使用的保护模式。系统复位和软件中断时进入该模式,在这个模式下,系统中断被关闭。
/*
* the actual reset code
*/
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0, cpsr
/*取当前程序状态寄存器CPSR到r0位置。*/
bic r0, r0, #0x1f
/*清除中断,只设置位模式控制位为中断提供服务 */
orr r0, r0, #0xd3
/*计算为超级保护模式*/
msr cpsr, r0
/*在将r0中的值写入状态寄存器CPSR*/
9.BL 为相对寻址,是以PC当前值为基地址,指令存储偏移地址及相对地址。系统通过BL完成跳转。
bl coloured_LED_init
bl red_LED_on
10.重定向u-boot代码加载到ARM内存中,这时对RM9进行的特殊处理。
#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
/*
* relocate exception table
*/
ldr r0, =_start /*取标号_start的绝对地址给r0,这个地址在链接时已确定 */
ldr r1, =0x0
mov r2, #16
copyex:
subs r2, r2, #1
ldr r3, [r0], #4
str r3, [r1], #4
bne copyex
#endif
11.这里配置寄存器,把进入系统运行所需要提前配置好的寄存器进行配置操作,设置好寄存器基地址。
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
/* turn off the watchdog */
# if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
/*中断控制寄存器*/
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
/*主中断屏蔽寄存器*/
# define CLKDIVN 0x14800014 /* clock divisor register */
/*时钟分频寄存器*/
#else
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
/*副中断屏蔽寄存器*/
# define CLKDIVN 0x4C000014 /* clock divisor register */
# endif
12. 系统屏蔽中断的具体操作。这就与我们只前说的,进入reset超级安全模式,系统会屏蔽中断具体就在这里操作的,设置“0“是禁止复位功能。
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/*将pWTCON所有位置为0*/
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
/*关闭所以中断*/
mov r1, #0xffffffff
ldr r0, =INTMSK
/*屏蔽所以中断, */
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
13.初始化设置系统时钟,FCLK的核心是提供时钟信号来完成分频操作,这里具体是配置分频寄存器。
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
/*选择是否配置cpu,ifndf是防止重复引用cpu_init_crit函数。*/
bl cpu_init_crit
#endif
14.查看地址信息,判断系统当前运行环境,针对不同环境判断是否进行重定向操作,也就是说在这里具体完成的功能判断是否已经在内存中运行,若是没有就将系统启动加载到内存中。
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
/*这时针对的nor flash中系统需要从flash中加载到内存中,而这句代码针对的就是这个过程中要启动的设备。*/
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
/*查看当前代码的地址信息,在不同情况中运行,_start的值不同。具体因为前面r0的操作不同*/
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
/*若cmp r0,r1相对,则说明是在RAM中建立堆栈,否则重定向代码,进行copy_loop复制操作从r0所在的起始地址开始。*/
ldr r2, _armboot_start
ldr r3, _bss_start
/*获取未初始化数据段地址*/
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
/*计算代码段和代码字节的长度*/
/*这里完成的是是一系列数据拷贝具体用于操作堆栈。*/
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
15.确定堆栈起点,初始化堆栈。
/* Set up the stack */
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */
sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo */
/* bdinfo 分配128字节来存储开发板信息,r0保存的是全局变量的起始地址*/
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
/*r0保存堆栈地址*/
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
16.这一段的作用是完成清理工作,将bss段空间清零,也就是完成数据段的初始化,清零工作。
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
/*清除非定义数据段*/
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
ldr pc, _start_armboot
/*这句话通过修改pc的值完成的就是从第一阶段,到第二阶段的跳转,通过这句话,将跳转到C语言代码继续运行。*/
_start_armboot: .word start_armboot
17.完成设置CPU前的初始化并设置关键的寄存器、初始化内存时序、时钟操作。
/*
*************************************************************************
* CPU_init_critical registers
* setup important registers
* setup memory timing
*************************************************************************
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*根据开发板硬件要求,设置RAM的时间*/
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
18.通过设置ip和pc的值来完成跳转到reset中执行lowlevel_init函数,再跳转回来的操作,从而完成系统的重定位。
mov ip, lr
bl lowlevel_init
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
…
通过对上述start.S的分析,我发现U-Boot启动是从ENTRY声明对象,跳转到被其声明对象位置开始运行,执行复位向量,配置异常中断向量,再次完成一次跳转后,开始正真的start,开始启动程序。设置好地址对齐规则,配置SVC模式,暂时屏蔽中断,,配置寄存器,配置时钟,配置初始化堆栈,然后加载到内存中去,最后完成数据段清零,从而完成U-Boot的第一步汇编部分的启动操作,然后跳转到C语言完成编写的第二阶段,运行程序。
文章中部分来源来自网络,各方总结完成,如有错误,欢迎大佬们批评指教。(鞠躬)