U-Boot完美解读(2)——启动文件start.s解析

2、谁来唤醒我

在上一节的u-boot.lds文件中有这样一句是:

cpu/arm_cortexa8/start.o (.text)
这句话就是调用初始化代码stat.s的元老级功臣,这可和神话中的女娲、盘古之类的有得一拼的,只是那时代没有计算机,要不还真得一较高低才行。说远了,回归主题,话说从这里调用并执行start.s文件后,该文件又是如何执行的呢?

2.1、天生我才必有用

start.s是就是所谓启动的第一阶段,其主要功能如下:

(1)定义入口。由于一个可执行的image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在rom(Flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。
(2)设置异常向量(exception vector)。
(3)设置CPU的速度、时钟频率及中断控制寄存器。
(4)初始化内存控制器 。
(5)将rom中的程序复制到ram中。
(6)初始化堆栈 。
(7)转到ram中执行,该工作可使用指令ldr pc来完成。


2.2、出问题了?相信表哥

怎么不是相信春哥得永生呢?其实我也这么想的,关键是程序不认识春哥呀,这个傻不啦叽的家伙只认识表哥呀,当然这里可不是你的表哥哟,所谓的表即“异常向量表”,明白了吧,关键时候还得看表哥的。

系统上电后,pc指针从0x00000000地址开始执行,这个地址是处理器可以直接访问的,所以这个时候不要指望能运行到你的外存上,实际上现在还在内部flash中,就是所谓的nor flash。所以,我们就要在0x00000000位置放置有意义的东西,不然系统怎么启动呢?废话少说,直接看下面的代码再作解释:

.globl _start
_start:    b   start_code
    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

该部分为处理器的异常处理向量表。地址范围为0x0000 0000 ~ 0x0000 0020,刚好8条指令,在ARM的异常向量表(如下表所示):

中断向量地址 异常中断类型 异常中断模式 优先级(6最低)
0x00 复位 特权模式 1
0x04 未定义的指令 UND终止模式 6
0x08 软件中断 特权模式 6
0x0C 指令预取终止 终止模式 5
0x10 数据访问终止 终止模式 2
0x14 保留 未使用 未使用
0x18 外部中断请求 IRQ模式 4
0x1C 快速中断请求 FIQ模式 3

FIQ异常向量放在所有向量最后,目的是可以将FIQ异常处理程序直接放在向量的地址上. 这样在执行FIQ处理时,就可以不用进行跳转,快速响应中断.FIQ向量放在最后,允许FIQ异常处理程序直接放在地址0x0000001C或0xFFFF001C开始 的位置,而不需要由向量的分支指令。也就是说直接从0x0000 0001C开始执行,这样省去了一个跳转指令,如果FIQ不是在顶端,那么当然需要一次跳转.

这里千万别理解为_start就是存放在0x00开始的地方,而是发生中断跳转的位置。而翻译后_start的位置由是编译链接工具决定,可由编译后u-boot目录下生成的System.map文件可知,_start的位置竟然是0x33f80000,那这个地址是哪儿来的呢?相信不是天上掉下来的林妹妹,我也想天上掉个林妹妹给我呢,就我这种码农,掉下来林妹妹也不见得是我的,更何况是根本不会实现的,想也白想了。因此,这个地址得有一个地方指定才对吧!OK,见证奇迹的时候到了,我们可以通过根目录下的Makefile查看是u-boot是如何链接的,有如下一段代码为证:

GEN_UBOOT = \
		UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
		sed  -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
		cd $(LNDIR) && $(LD) $(LDFLAGS) $(LDFLAGS_$(@F)) $$UNDEF_SYM $(__OBJS) \
			--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
			-Map u-boot.map -o u-boot
$(obj)u-boot:	depend \
		$(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
		$(GEN_UBOOT)

可以看到LDFLAGS标志,而它又被定义了,所以得找到罪魁祸首才行,在根目录下的config.mk文件中,可以看到如下代码:

LDFLAGS += -Bstatic -T $(obj)u-boot.lds $(PLATFORM_LDFLAGS)
ifneq ($(TEXT_BASE),)
LDFLAGS += -Ttext $(TEXT_BASE)
endif

LDFLAGS里面除了指定链接脚本,如果TEXT_BASE不等于空,还加上了-Ttext$(TEXT_BASE),TEXT_BASE的值是多少呢?我们可以在config.mk里面看到它的定义(系统中有多个文件,不同处理器对应的文件不同,如移植s3c2440的可以参见目录board/samsung/smdk2410/,当然也要防止U-Boot的结构调整,指不定这个目录又放到哪儿去了),亲,看到了吗?就只有这一行哟,它的值为0x33f80000。

TEXT_BASE = 0x33F80000

关于异常向量表就啰嗦说这么多了,不过还是没觉得轻松多少呢?因为真正的解释部分还没开始,接下来正文开始了,首先说明一下第一行,学习过汇编的童鞋都应该知道globl是声明全局变量的意思,所以这里把_start声明是全局的,至于全局变量和局部变量的区别,相信各位童鞋的能力,就此略去三千字吧。

程序段正文的第一行可是一个亮点哟,亲们,这里不能打瞌睡哟!

_start:    b       start_code

程序开始的第一句就是一个跳转,而且是直接跳转的哟,可没有半点含糊,表示的直接启动或重启,为什么会有重启功能呢?因为第一次开机,和热启动都要从这里执行,只是冷启动前有一次上电操作,而热启动直接进入中断向量表,调用这条指令执行start_code,而且还要记得热启动设备一直都处于带电状态,现在是不是也明白了我们的电脑有热启动和冷启动的差别了?

只用说明第一条是跳转执行的,那接下来的几句也不用多作说明了吧,分别在对应异常的发生时进行相应的跳转,跳转的位置可以通过接下来的几行代码来看:

_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
例如,当发生fiq中断时,将进行跳转到对应的中断,而对应的fiq标签可以在文件后面找到相应的实现部分(其实这里还有一个分支,仅用一个作示例):

fiq:
	get_bad_stack
	bad_save_user_regs
	bl	do_fiq
再回到上面的中断发生处理中,在标签前面还有一个.word,在汇编里.word伪操作用于分配一段字内存单元(分配的单元都是字对齐的)
然后接下来有一个很变态的句子,我也一直在想,结果还是在网上找了一些参考,找到一个合理的解释,差点忘了,先把代码贴出来:

.balignl 16,0xdeadbeef
首先来解释哈.balignl的应用形式:
.balign[wl] abs-expr, abs-expr, abs-expr
增加位置计数器(在当前子段)使它指向规定的存储边界。第一个表达式参数(结果必须是纯粹的数字)是必需参数:边界基准,单位为字节。例如,‘.balign 8’向后移动位置计数器直至计数器的值等于8的倍数。如果位置计数器已经是8的倍数,则无需移动。第2个表达式参数(结果必须是纯粹的数字)给出填充字节 的值,用这个值填充位置计数器越过的地方。第2个参数(和逗点)可以省略。如果省略它,填充字节的值通常是0。但在某些系统上,如果本段标识为包含代码, 而填充值被省略,则使用no-op指令填充空白区。第3个参数的结果也必须是纯粹的数字,这个参数是可选的。如果存在第3个参数,它代表本对齐命令允许跳 过字节数的最大值。如果完成这个对齐需要跳过的字节数比规定的最大值还多,则根本无法完成对齐。您可以在边界基准参数后简单地使用两个逗号,以省略填充值 参数(第二参数);如果您在想在适当的时候,对齐操作自动使用no-op指令填充,本方法将非常奏效。.balignw和.balignl是.balign命令的变化形式。.balignw使用2个字节来填充空白区。.balignl使用4字节来填充。例如,.balignw 4,0x368d将地址对齐到4的倍数,如果它跳过2个字节,GAS将使用0x368d填充这2个字节(字节的确切存放位置视处理器的存储方式而定)。

ARM920T处理器核心,支持32位与16位两种指令长度,16位的指令叫thumb指令集,由于我使用的是32位指令集,所以一切都是以32位指令集进行说明。既然是32位指令集,所以一条指令就占32位,即4字节。

而在前面的两段代码中,可以通过计算,第一部分占了4x8=32字节内存;第二部分占了4x7=28字节内存。一共占了4x15=60个字节的内存,所以本代码的作者当时就简单的在15这个数上,加了个1,即16,把当前指针往后移到地址为64的位置,然后在前面插上了0xdeadbeef这个特殊的值。果说最小的值的话,那么也可以写成.balignl 8,0xdeadbeef,也可以达到同样的目的。因为60不是8的倍数,但是64是8的倍数,如果写8,也正好插到64前面,也即60这个内存起始地址。如果更大一点儿的呢,那么填32也可以达到同样的效果,即.balignl 32,0xdeadbeef,道理同上。当然,不能为4,因为pc值在任何时候,都是4的倍数,只要不为0就为4的倍数,呵呵,这个值不行,如果用了这个值,0xdeadbeef永远也插不进去。-----------------以上解释引自http://haoyeren.blog.sohu.com/84511571.html

这里再补充一点为什么用‘.balign 4’就不行,因为可以计算出当然PC的位置是60个字节,前面也有计算,所以4x15=60正好是4的倍数,无需移动,而指令的使用是用0xdeadbeef填充移动的空间,因为没有移动,所以中间没有空余的空间不能插呀!

2.3、山高我为峰

前面对我们的表哥进行了算是详细的介绍,但正如前面所说的那样,系统上电后运行到第一句就跳走了,后面的是在异常发生时再进行跳转,所以真正引导系统启动的代码还没有开始哟!亲,辛苦了哟,现在就是开始的代码:

/*
 * the actual start code
 */

start_code:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0, cpsr
	bic	r0, r0, #0x1f
	orr	r0, r0, #0xd3
	msr	cpsr, r0
从以上的注释也可以看到,这里是将CPU的模式为设置为SVC模式,至于SVC是什么模式,这里得说明一下处理器硬件的东西了。ARM处理器共有37个寄存器,31个通用寄存器,分别为R0-R15,其中R8-R12为分组寄存器,R13可理解为堆栈指针(汇编里用的SP就是鼎鼎有名的R13),R14为链接寄存器(汇编里可也有这位大侠的句号哟,就是所用的LR链接寄存器),至于R15就更不用说了,就是传说中滴PC大神,程序计数器。除了31个通用寄存器还有6个状态寄存器,分为CPSR和SPSR,其中SPSR有五个分支,就是为处理器5种状态(fiq、irq、svc、abt、und,除此之外还有系统模式和用户模式。这里涉及到的模式就由此展开,不同的模式下可访问的资源权限是不同的,接下来通过一个表格详细介绍一下ARM中CPU的几种模式:

处理器模式 说明 主要功能
用户(usr) 正常程序工作模式 此模式下程序不能够访问一些受操作系统保护的系统资源,应用程序也不能直接进行处理器模式的切换。
系统(sys) 用于支持操作系统的特权任务等 与用户模式类似,但具有可以直接切换到其它模式等特权
快中断(fiq) 支持高速数据传输及通道处理 FIQ异常响应时进入此模式
中断(irq) 用于通用中断处理 IRQ异常响应时进入此模式
管理(svc) 操作系统保护代码 系统复位和软件中断响应时进入此模式
中止(abt) 用于支持虚拟内存和/或存储器保护 在ARM7TDMI没有大用处
未定义(und) 支持硬件协处理器的软件仿真 未定义指令异常响应时进入此模式

sys模式和usr模式相比,所用的寄存器组,都是一样的,但是增加了一些访问一些在usr模式下不能访问的资源。而svc模式本身就属于特权模式,本身就可以访问那些受控资源,而且,比sys模式还多了些自己模式下的影子寄存器,所以,相对sys模式来说,可以访问资源的能力相同,但是拥有更多的硬件资源。所以,从理论上来说,虽然可以设置为sys和svc模式的任一种,但是从uboot方面考虑,其要做的事情是初始化系统相关硬件资源,需要获取尽量多的权限,以方便操作硬件,初始化硬件。


2.4、关门,放狗!

狗,很形象的东西,如果不喂狗就会乱咬人

#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         0x4A0000
#  define CLKDIVN     0x4C000014       /* clock divisor register */
# endif
 
         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


如上代码中,前面的
#if defined....
.....
#else
....
#endif
是定义到寄存器,就是给寄存器取了一个别名,如同是我们的名字一样。如果在其它地方有定义,直接引入就可以了,不必重新定义,这里就不用多说了,此处略去一千字解释张三、李四名字的由来......
接下来通过ldr指向看门狗寄存器,下面就直接向狗写入0,这里就是一个关狗的过程。看门狗的原理就是:当看门狗开启后,如果一段时间不去喂狗,狗就会饿呀,狗饿了做干嘛呢,抢答题?你系统就会不断的一直重启,重启,还是重启。这里就是为什么在刚开始时会把狗关起来的原因,防止喂狗的时间到了,狗饿死了,然后你就挂了。
最后就是关中断,因为系统在初始化的时候不需要中断处理,如果打开的时候,你突然按一下按键,跳去处理按键,但系统还没初始化完呢?会出现什么情况呢,一样的结局,还没看到开机界面呢,挂了。

你可能感兴趣的:(U-Boot完美解读(2)——启动文件start.s解析)