之前写的硬件相关的裸机源码的流程和uboot差不多。之前的实验主要完成以下操作:
(1)初始化:关看门狗,初始化时钟,初始化sdram。
(2)程序很大时,把程序从nandflash拷贝到sdaram。
(3)调用C函数必须设置栈sp
一、cpu/arm920t/start.s启动文件分析
.globl _start
/*跳转到reset*/
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_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
.balignl 16,0xdeadbeef
reset:
/*
* set the cpu to SVC32 mode 管理模式
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
/* turn off the watchdog 关看门狗*/
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMOD 0X4A000004
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
#endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/*
* 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
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
* CPU初始化
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
/*读地址指令 如果是上电后运行,代码是从nandflash自动拷贝到sdram,则r0为0.如果是通过下载器下载到sdram,则r0为他的链接地址0x33f8000*/
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
/*不相等则证明sdram还没有被初始化。*/
cmp r0, r1 /* don't reloc during debug */
blne cpu_init_crit
#endif
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches 关flash
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches 清mmu
*/
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.
*/
mov ip, lr
/*初始化存储控制器,初始化之后内存才可以使用*/
bl lowlevel_init
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
/* Set up the stack 设置栈 */
/*栈会一直减下去,指向不同的位置*/
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot r0=0x33f80000 */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area r0 =r0-CFG_MALLOC_LEN */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo r0 =r0-CFG_GBL_DATA_SIZE */
#ifdef CONFIG_USE_IRQ
/*r0=r0-(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)*/
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack r0=r0-12 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
/*调用初始化时钟的C函数*/
bl clock_init
#endif
/*把代码从flash读到sdram链接地址*/
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq clear_bss
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
/*清楚BSS段,初始化为0的静态变量或者全局变量*/
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
ldr pc, _start_armboot
/*调用c函数start_armboot*/
_start_armboot: .word start_armboot
小结:设置管理模式,关看门狗,屏蔽中断,sdram的初始化,设置栈,设置时钟,代码重定位,清除bss段(之前的都称为uboot的第一阶段),调用C函数start_armboot(第二阶段开始)。
二、清除BSS段原因
BSS段清零的原因是因为这个段是BSS
要说为什么要有BSS的话,历史就比较久远了。 BSS段我所知道的起源是Unix最初的时候(当然,不排除可能有更早的情况)。变量分两种:局部变量、全局变量。
根据C语法的规定,局部变量不设置初始值的时候,其初始值是不确定的,局部变量(不含静态局部变量)的存储位置位于栈上,具体位置不固定。
全局变量(和静态局部变量)有专门数据段存储,初始值是0,具体位置是固定的。
其实说到底,就两种,一种是位置固定(数据段里),一种是位置不固定的(栈上)。
要知道,早期的计算机存储设备是很贵的,而很多时候,数据段里的全局变量都是0(或者没有初始值),那么存储这么多的0到目标文件里其实是没有必要的。所以为了节约空间,在生成目标文件的时候,就把没有初始值(实际就是0)的数据段里的变量都放到BSS段里,这样目标文件就不需要那么大的体积里(节约磁盘空间)。只有当目标文件被载入的时候,加载器负责把BSS段清零(一个循环就可以搞定)。 之后,这个规则慢慢的成为一个标准配置,大多数编译器也就都支持了BSS段。
然后解释几个问题: Q:为什么局部变量初始值不是0? A:局部变量初始值也可以是零(在某些语言中就是),但这实际上需要消耗硬件指令去完成,有些时候这种清零的动作意义不大,对于编译器来说也是一种负担,每次调用函数都要消耗指令去清零,负担太大。要知道全局变量在内存中只有一份,局部变量(非静态)可以是多份的,前者一次清零就可以了,后者多次清零,负担太大。
Q:如果BSS不清零可不可以? A:可以,如果编译器规定BSS段不清零,也是可以的,但这样的话C语言语法就要改了:未初始化的全局变量和静态局部变量,其值是未知的。甚至其它语言也要跟着改语法。
所以,BSS段清零的原因是因为这个段是BSS 现在存储介质这么便宜了,是不是BSS已经没有必要了?当然不是了,介质便宜仅限于PC和数码产品这一块,嵌入式行业永远都不存在存储介质没有限制的情况。